@chatwarden/player-react 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/styles.css CHANGED
@@ -393,60 +393,19 @@
393
393
  }
394
394
 
395
395
  /* ---- The report form --------------------------------------------------------------------------------------------- */
396
- .cw-player .cw-fieldset {
396
+ .cw-player .cw-field {
397
397
  min-width: 0;
398
- margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
399
- border: 0;
400
- }
401
- .cw-player .cw-fieldset[aria-invalid='true'] {
402
- border-left: 3px solid var(--cw-red, var(--cwd-red));
403
- padding-left: var(--cw-space-3, var(--cwd-space-3));
404
- }
405
- .cw-player .cw-legend {
406
- margin-bottom: var(--cw-space-2, var(--cwd-space-2));
407
- padding: 0;
408
- font-weight: 600;
409
- }
410
- .cw-player .cw-choice {
411
- display: flex;
412
- align-items: center;
413
- gap: var(--cw-space-3, var(--cwd-space-3));
414
- min-height: var(--cw-control-height, var(--cwd-control-height));
415
- padding: 0 var(--cw-space-3, var(--cwd-space-3));
416
- border: var(--cw-border-width, var(--cwd-border-width)) solid var(--cw-border, var(--cwd-border));
417
- border-radius: var(--cw-radius-s, var(--cwd-radius-s));
418
- cursor: pointer;
419
- }
420
- .cw-player .cw-choice + .cw-choice {
421
- margin-top: var(--cw-space-1, var(--cwd-space-1));
398
+ margin-bottom: var(--cw-space-3, var(--cwd-space-3));
422
399
  }
423
- .cw-player .cw-choice:has(input:checked) {
424
- border-color: var(--cw-accent, var(--cwd-accent));
425
- background: var(--cw-accent-soft, var(--cwd-accent-soft));
400
+ .cw-player .cw-field > .cw-hint {
401
+ margin: var(--cw-space-1, var(--cwd-space-1)) 0 0;
426
402
  }
427
- .cw-player .cw-choice-text {
428
- display: flex;
429
- flex-direction: column;
403
+ .cw-player .cw-extra {
430
404
  min-width: 0;
431
- padding: var(--cw-space-2, var(--cwd-space-2)) 0;
432
- overflow-wrap: anywhere;
433
- }
434
- .cw-player .cw-choice-hint {
435
- color: var(--cw-ink-muted, var(--cwd-ink-muted));
436
- font-size: var(--cw-text-s, var(--cwd-text-s));
405
+ margin-bottom: var(--cw-space-3, var(--cwd-space-3));
437
406
  }
438
- .cw-player .cw-group {
439
- min-width: 0;
440
- margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
441
- padding: 0;
442
- border: 0;
443
- }
444
- .cw-player .cw-group-legend {
445
- margin-bottom: var(--cw-space-1, var(--cwd-space-1));
446
- padding: 0;
447
- color: var(--cw-ink-muted, var(--cwd-ink-muted));
448
- font-size: var(--cw-text-s, var(--cwd-text-s));
449
- font-weight: 600;
407
+ .cw-player .cw-field > .cw-field-error {
408
+ margin: var(--cw-space-2, var(--cwd-space-2)) 0 0;
450
409
  }
451
410
  .cw-player .cw-quote {
452
411
  margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
@@ -460,34 +419,59 @@
460
419
  gap: var(--cw-space-2, var(--cwd-space-2));
461
420
  margin-bottom: var(--cw-space-3, var(--cwd-space-3));
462
421
  }
463
- .cw-player .cw-disclosure {
464
- margin-bottom: var(--cw-space-3, var(--cwd-space-3));
465
- }
466
- .cw-player .cw-panel {
467
- min-width: 0;
468
- margin-bottom: var(--cw-space-3, var(--cwd-space-3));
469
- }
470
- .cw-player input[type='radio'] {
471
- flex: none;
472
- accent-color: var(--cw-accent, var(--cwd-accent));
473
- }
474
422
  .cw-player .cw-label {
475
423
  display: block;
476
424
  margin-bottom: var(--cw-space-1, var(--cwd-space-1));
477
425
  font-weight: 600;
478
426
  }
479
- .cw-player textarea {
427
+ .cw-player textarea,
428
+ .cw-player select {
480
429
  display: block;
481
430
  width: 100%;
482
431
  max-width: 100%;
432
+ min-width: 0;
483
433
  padding: var(--cw-space-2, var(--cwd-space-2)) var(--cw-space-3, var(--cwd-space-3));
484
434
  border: var(--cw-border-width, var(--cwd-border-width)) solid var(--cw-border-strong, var(--cwd-border-strong));
485
435
  border-radius: var(--cw-radius-s, var(--cwd-radius-s));
486
436
  background: var(--cw-surface, var(--cwd-surface));
487
437
  color: var(--cw-ink, var(--cwd-ink));
488
438
  font: inherit;
439
+ }
440
+ .cw-player textarea {
489
441
  resize: vertical;
490
442
  }
443
+ .cw-player select {
444
+ min-height: var(--cw-control-height, var(--cwd-control-height));
445
+ text-overflow: ellipsis;
446
+ }
447
+ /* A reason chosen is a choice you made: the accent, as the old radio was. */
448
+ .cw-player select:has(option:checked:not([value=''])) {
449
+ border-color: var(--cw-accent, var(--cwd-accent));
450
+ background: var(--cw-accent-soft, var(--cwd-accent-soft));
451
+ }
452
+ .cw-player select[aria-invalid='true'] {
453
+ border-color: var(--cw-red, var(--cwd-red));
454
+ }
455
+ /* The one door to the advanced part: a text button, small, in the accent (it is the next action). */
456
+ .cw-player .cw-linkbtn {
457
+ min-height: 2rem;
458
+ margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
459
+ padding: 0;
460
+ border: 0;
461
+ background: none;
462
+ color: var(--cw-accent, var(--cwd-accent));
463
+ font: inherit;
464
+ font-size: var(--cw-text-s, var(--cwd-text-s));
465
+ text-decoration: underline;
466
+ text-underline-offset: 0.2em;
467
+ cursor: pointer;
468
+ }
469
+ /* A thumb needs the same target as the other buttons. */
470
+ @media (pointer: coarse) {
471
+ .cw-player .cw-linkbtn {
472
+ min-height: var(--cw-control-height, var(--cwd-control-height));
473
+ }
474
+ }
491
475
  .cw-player textarea + .cw-hint {
492
476
  margin-top: var(--cw-space-1, var(--cwd-space-1));
493
477
  }
@@ -1,35 +0,0 @@
1
- import type { MutableRefObject } from 'react';
2
- import type { ReportDetailResult } from '@chatwarden/core';
3
- import type { PlayerText } from './i18n.js';
4
- import type { ReasonGroup } from './reporting.js';
5
- import type { PlayerT } from './i18n.js';
6
- /** What the player adds after a one-tap report. Your server calls `cw.reports.addDetail(reportId, reporterSeat, detail)`. */
7
- export interface ReportFollowUp {
8
- reason?: string;
9
- note?: string;
10
- }
11
- /** How the follow-up ended, for the dialog to say: added, or the refusal word (an unknown one reads as a failure). */
12
- export type FollowUpOutcome = {
13
- ok: true;
14
- } | {
15
- ok: false;
16
- refused: string;
17
- };
18
- /**
19
- * The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
20
- * `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
21
- * dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
22
- */
23
- export declare function FollowUp(props: {
24
- t: PlayerT;
25
- groups: readonly ReasonGroup[];
26
- reportId: string;
27
- onAddDetail: (reportId: string, detail: ReportFollowUp) => Promise<ReportDetailResult>;
28
- sending: MutableRefObject<boolean>;
29
- busy: boolean;
30
- onBusy: (busy: boolean) => void;
31
- onDone: (outcome: FollowUpOutcome) => void;
32
- }): import("react").JSX.Element;
33
- /** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
34
- export declare const DETAIL_REFUSED_TEXT: Readonly<Record<string, PlayerText>>;
35
- //# sourceMappingURL=FollowUp.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FollowUp.d.ts","sourceRoot":"","sources":["../../src/FollowUp.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,6HAA6H;AAC7H,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sHAAsH;AACtH,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAM5E;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,CAAC,EAAE,OAAO,CAAC;IACX,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvF,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C,+BA0FA;AAED,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAOpE,CAAC"}
@@ -1,69 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId, useState } from 'react';
3
- import { NOTE_MAX } from './constants.js';
4
- import { PlayerError } from './client.js';
5
- import { Picker } from './Picker.js';
6
- import { errorText } from './ui.js';
7
- /** A failure to send as an error the components can word: anything that is not already a PlayerError is a dropped connection. */
8
- const failureOf = (e) => e instanceof PlayerError ? e : new PlayerError({ status: 0, code: 'network', message: 'Could not reach the server.' });
9
- /**
10
- * The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
11
- * `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
12
- * dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
13
- */
14
- export function FollowUp(props) {
15
- const { t, groups, reportId, onAddDetail, sending, busy, onBusy, onDone } = props;
16
- const name = useId();
17
- const noteId = useId();
18
- const missingId = useId();
19
- const panelId = useId();
20
- const [open, setOpen] = useState(false);
21
- const [reason, setReason] = useState(null);
22
- const [note, setNote] = useState('');
23
- const [missing, setMissing] = useState(false);
24
- const [failure, setFailure] = useState(null);
25
- const trimmed = note.trim();
26
- const submit = async (e) => {
27
- e.preventDefault();
28
- if (sending.current)
29
- return;
30
- if (reason === null && trimmed === '') {
31
- setMissing(true);
32
- return;
33
- }
34
- sending.current = true;
35
- onBusy(true);
36
- setFailure(null);
37
- try {
38
- const detail = { ...(reason === null ? {} : { reason }), ...(trimmed === '' ? {} : { note: trimmed }) };
39
- const raw = await onAddDetail(reportId, detail);
40
- // Anything that is not a result object (undefined, null, a string) is not a success and not a crash: it reads as an unknown refusal.
41
- const answer = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? raw : { ok: false, refused: 'unknown' };
42
- onDone(answer.ok === true ? { ok: true } : { ok: false, refused: String(answer.refused) });
43
- }
44
- catch (error) {
45
- setFailure(failureOf(error));
46
- }
47
- finally {
48
- sending.current = false;
49
- onBusy(false);
50
- }
51
- };
52
- return (_jsxs("form", { onSubmit: (e) => void submit(e), noValidate: true, children: [_jsx("button", { type: "button", className: "cw-btn cw-disclosure", "aria-expanded": open, "aria-controls": open ? panelId : undefined, onClick: () => setOpen(!open), children: t('player.report.addReason') }), open && (_jsxs("div", { id: panelId, className: "cw-panel", children: [_jsx("p", { className: "cw-hint", children: t('player.report.addReasonHint') }), _jsx(Picker, { groups: groups, name: name, value: reason, legend: t('player.report.detail.legend'), invalid: missing, describedBy: missingId, onChange: (id) => {
53
- setReason(id);
54
- setMissing(false);
55
- } }), missing && (_jsx("p", { id: missingId, className: "cw-field-error", role: "alert", children: t('player.report.detail.need') })), _jsx("label", { className: "cw-label", htmlFor: noteId, children: t('player.report.note') }), _jsx("textarea", { id: noteId, className: "cw-textarea", rows: 3, maxLength: NOTE_MAX, value: note, onChange: (e) => {
56
- setNote(e.target.value);
57
- setMissing(false);
58
- } }), failure !== null && (_jsxs("p", { className: "cw-error", role: "alert", children: [t('player.report.detail.failed'), " ", errorText(t, failure)] })), _jsx("div", { className: "cw-actions", children: _jsx("button", { type: "submit", className: "cw-btn cw-btn--primary", "aria-disabled": busy || undefined, "aria-busy": busy || undefined, children: t('player.report.detail.send') }) })] }))] }));
59
- }
60
- /** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
61
- export const DETAIL_REFUSED_TEXT = {
62
- off: 'player.report.detail.refused.off',
63
- not_found: 'player.report.detail.refused.not_found',
64
- not_reporter: 'player.report.detail.refused.not_reporter',
65
- too_late: 'player.report.detail.refused.too_late',
66
- already_added: 'player.report.detail.refused.already_added',
67
- reason_unknown: 'player.report.detail.refused.reason_unknown',
68
- };
69
- //# sourceMappingURL=FollowUp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FollowUp.js","sourceRoot":"","sources":["../../src/FollowUp.tsx"],"names":[],"mappings":"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"}
@@ -1,14 +0,0 @@
1
- import type { RefObject } from 'react';
2
- import type { ReasonGroup } from './reporting.js';
3
- /** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
4
- export declare function Picker(props: {
5
- groups: readonly ReasonGroup[];
6
- name: string;
7
- value: string | null;
8
- onChange: (id: string) => void;
9
- legend: string;
10
- invalid?: boolean;
11
- describedBy?: string | undefined;
12
- fieldset?: RefObject<HTMLFieldSetElement | null>;
13
- }): import("react").JSX.Element;
14
- //# sourceMappingURL=Picker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Picker.d.ts","sourceRoot":"","sources":["../../src/Picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,gBAAgB,CAAC;AAE9D,iIAAiI;AACjI,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;CAClD,+BA6CA"}
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId } from 'react';
3
- /** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
4
- export function Picker(props) {
5
- const { groups, name, value, onChange, legend, invalid, describedBy, fieldset } = props;
6
- const base = useId();
7
- const choice = (r) => {
8
- const label = `${base}-${r.id}-l`;
9
- const hint = `${base}-${r.id}-h`;
10
- return (_jsxs("label", { className: "cw-choice", children: [_jsx("input", { type: "radio", name: name, value: r.id, checked: value === r.id, "aria-labelledby": label, "aria-describedby": r.hint === '' ? undefined : hint, onChange: () => onChange(r.id) }), _jsxs("span", { className: "cw-choice-text", children: [_jsx("span", { id: label, children: r.label }), r.hint !== '' && (_jsx("span", { id: hint, className: "cw-choice-hint", children: r.hint }))] })] }, r.id));
11
- };
12
- return (_jsxs("fieldset", { ref: fieldset, className: "cw-fieldset", role: "radiogroup", "aria-invalid": invalid || undefined, "aria-describedby": invalid ? describedBy : undefined, children: [_jsx("legend", { className: "cw-legend", children: legend }), groups.map((g, i) => g.name === '' ? (_jsx("div", { className: "cw-group", children: g.items.map(choice) }, `g${i}`)) : (_jsxs("fieldset", { className: "cw-fieldset cw-group", children: [_jsx("legend", { className: "cw-group-legend", children: g.name }), g.items.map(choice)] }, `g${i}`)))] }));
13
- }
14
- //# sourceMappingURL=Picker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Picker.js","sourceRoot":"","sources":["../../src/Picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAI9B,iIAAiI;AACjI,MAAM,UAAU,MAAM,CAAC,KAStB;IACC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACxF,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,CAAC,CAAa,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;QAClC,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;QACjC,OAAO,CACL,iBAAkB,SAAS,EAAC,WAAW,aACrC,gBACE,IAAI,EAAC,OAAO,EACZ,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,CAAC,CAAC,EAAE,EACX,OAAO,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,qBACN,KAAK,sBACJ,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAClD,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAC9B,EACF,gBAAM,SAAS,EAAC,gBAAgB,aAC9B,eAAM,EAAE,EAAE,KAAK,YAAG,CAAC,CAAC,KAAK,GAAQ,EAChC,CAAC,CAAC,IAAI,KAAK,EAAE,IAAI,CAChB,eAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAC,gBAAgB,YACvC,CAAC,CAAC,IAAI,GACF,CACR,IACI,KAjBG,CAAC,CAAC,EAAE,CAkBR,CACT,CAAC;IACJ,CAAC,CAAC;IACF,OAAO,CACL,oBAAU,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,aAAa,EAAC,IAAI,EAAC,YAAY,kBAAe,OAAO,IAAI,SAAS,sBAAoB,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,aACxJ,iBAAQ,SAAS,EAAC,WAAW,YAAE,MAAM,GAAU,EAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACnB,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CACd,cAAmB,SAAS,EAAC,UAAU,YACpC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IADZ,IAAI,CAAC,EAAE,CAEX,CACP,CAAC,CAAC,CAAC,CACF,oBAAwB,SAAS,EAAC,sBAAsB,aACtD,iBAAQ,SAAS,EAAC,iBAAiB,YAAE,CAAC,CAAC,IAAI,GAAU,EACpD,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAFP,IAAI,CAAC,EAAE,CAGX,CACZ,CACF,IACQ,CACZ,CAAC;AACJ,CAAC"}
package/src/FollowUp.tsx DELETED
@@ -1,139 +0,0 @@
1
- import { useId, useState } from 'react';
2
- import type { FormEvent, MutableRefObject } from 'react';
3
- import type { ReportDetailResult } from '@chatwarden/core';
4
- import { NOTE_MAX } from './constants.js';
5
- import { PlayerError } from './client.js';
6
- import type { PlayerText } from './i18n.js';
7
- import { Picker } from './Picker.js';
8
- import type { ReasonGroup } from './reporting.js';
9
- import { errorText } from './ui.js';
10
- import type { PlayerT } from './i18n.js';
11
-
12
- /** What the player adds after a one-tap report. Your server calls `cw.reports.addDetail(reportId, reporterSeat, detail)`. */
13
- export interface ReportFollowUp {
14
- reason?: string;
15
- note?: string;
16
- }
17
-
18
- /** How the follow-up ended, for the dialog to say: added, or the refusal word (an unknown one reads as a failure). */
19
- export type FollowUpOutcome = { ok: true } | { ok: false; refused: string };
20
-
21
- /** A failure to send as an error the components can word: anything that is not already a PlayerError is a dropped connection. */
22
- const failureOf = (e: unknown): PlayerError =>
23
- e instanceof PlayerError ? e : new PlayerError({ status: 0, code: 'network', message: 'Could not reach the server.' });
24
-
25
- /**
26
- * The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
27
- * `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
28
- * dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
29
- */
30
- export function FollowUp(props: {
31
- t: PlayerT;
32
- groups: readonly ReasonGroup[];
33
- reportId: string;
34
- onAddDetail: (reportId: string, detail: ReportFollowUp) => Promise<ReportDetailResult>;
35
- sending: MutableRefObject<boolean>;
36
- busy: boolean;
37
- onBusy: (busy: boolean) => void;
38
- onDone: (outcome: FollowUpOutcome) => void;
39
- }) {
40
- const { t, groups, reportId, onAddDetail, sending, busy, onBusy, onDone } = props;
41
- const name = useId();
42
- const noteId = useId();
43
- const missingId = useId();
44
- const panelId = useId();
45
- const [open, setOpen] = useState(false);
46
- const [reason, setReason] = useState<string | null>(null);
47
- const [note, setNote] = useState('');
48
- const [missing, setMissing] = useState(false);
49
- const [failure, setFailure] = useState<PlayerError | null>(null);
50
-
51
- const trimmed = note.trim();
52
- const submit = async (e: FormEvent): Promise<void> => {
53
- e.preventDefault();
54
- if (sending.current) return;
55
- if (reason === null && trimmed === '') {
56
- setMissing(true);
57
- return;
58
- }
59
- sending.current = true;
60
- onBusy(true);
61
- setFailure(null);
62
- try {
63
- const detail: ReportFollowUp = { ...(reason === null ? {} : { reason }), ...(trimmed === '' ? {} : { note: trimmed }) };
64
- const raw: unknown = await onAddDetail(reportId, detail);
65
- // Anything that is not a result object (undefined, null, a string) is not a success and not a crash: it reads as an unknown refusal.
66
- const answer = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? (raw as ReportDetailResult) : ({ ok: false, refused: 'unknown' } as unknown as ReportDetailResult);
67
- onDone(answer.ok === true ? { ok: true } : { ok: false, refused: String((answer as { refused?: unknown }).refused) });
68
- } catch (error) {
69
- setFailure(failureOf(error));
70
- } finally {
71
- sending.current = false;
72
- onBusy(false);
73
- }
74
- };
75
-
76
- return (
77
- <form onSubmit={(e) => void submit(e)} noValidate>
78
- <button type="button" className="cw-btn cw-disclosure" aria-expanded={open} aria-controls={open ? panelId : undefined} onClick={() => setOpen(!open)}>
79
- {t('player.report.addReason')}
80
- </button>
81
- {open && (
82
- <div id={panelId} className="cw-panel">
83
- <p className="cw-hint">{t('player.report.addReasonHint')}</p>
84
- <Picker
85
- groups={groups}
86
- name={name}
87
- value={reason}
88
- legend={t('player.report.detail.legend')}
89
- invalid={missing}
90
- describedBy={missingId}
91
- onChange={(id) => {
92
- setReason(id);
93
- setMissing(false);
94
- }}
95
- />
96
- {missing && (
97
- <p id={missingId} className="cw-field-error" role="alert">
98
- {t('player.report.detail.need')}
99
- </p>
100
- )}
101
- <label className="cw-label" htmlFor={noteId}>
102
- {t('player.report.note')}
103
- </label>
104
- <textarea
105
- id={noteId}
106
- className="cw-textarea"
107
- rows={3}
108
- maxLength={NOTE_MAX}
109
- value={note}
110
- onChange={(e) => {
111
- setNote(e.target.value);
112
- setMissing(false);
113
- }}
114
- />
115
- {failure !== null && (
116
- <p className="cw-error" role="alert">
117
- {t('player.report.detail.failed')} {errorText(t, failure)}
118
- </p>
119
- )}
120
- <div className="cw-actions">
121
- <button type="submit" className="cw-btn cw-btn--primary" aria-disabled={busy || undefined} aria-busy={busy || undefined}>
122
- {t('player.report.detail.send')}
123
- </button>
124
- </div>
125
- </div>
126
- )}
127
- </form>
128
- );
129
- }
130
-
131
- /** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
132
- export const DETAIL_REFUSED_TEXT: Readonly<Record<string, PlayerText>> = {
133
- off: 'player.report.detail.refused.off',
134
- not_found: 'player.report.detail.refused.not_found',
135
- not_reporter: 'player.report.detail.refused.not_reporter',
136
- too_late: 'player.report.detail.refused.too_late',
137
- already_added: 'player.report.detail.refused.already_added',
138
- reason_unknown: 'player.report.detail.refused.reason_unknown',
139
- };
package/src/Picker.tsx DELETED
@@ -1,60 +0,0 @@
1
- import { useId } from 'react';
2
- import type { RefObject } from 'react';
3
- import type { ReasonGroup, ReasonItem } from './reporting.js';
4
-
5
- /** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
6
- export function Picker(props: {
7
- groups: readonly ReasonGroup[];
8
- name: string;
9
- value: string | null;
10
- onChange: (id: string) => void;
11
- legend: string;
12
- invalid?: boolean;
13
- describedBy?: string | undefined;
14
- fieldset?: RefObject<HTMLFieldSetElement | null>;
15
- }) {
16
- const { groups, name, value, onChange, legend, invalid, describedBy, fieldset } = props;
17
- const base = useId();
18
- const choice = (r: ReasonItem) => {
19
- const label = `${base}-${r.id}-l`;
20
- const hint = `${base}-${r.id}-h`;
21
- return (
22
- <label key={r.id} className="cw-choice">
23
- <input
24
- type="radio"
25
- name={name}
26
- value={r.id}
27
- checked={value === r.id}
28
- aria-labelledby={label}
29
- aria-describedby={r.hint === '' ? undefined : hint}
30
- onChange={() => onChange(r.id)}
31
- />
32
- <span className="cw-choice-text">
33
- <span id={label}>{r.label}</span>
34
- {r.hint !== '' && (
35
- <span id={hint} className="cw-choice-hint">
36
- {r.hint}
37
- </span>
38
- )}
39
- </span>
40
- </label>
41
- );
42
- };
43
- return (
44
- <fieldset ref={fieldset} className="cw-fieldset" role="radiogroup" aria-invalid={invalid || undefined} aria-describedby={invalid ? describedBy : undefined}>
45
- <legend className="cw-legend">{legend}</legend>
46
- {groups.map((g, i) =>
47
- g.name === '' ? (
48
- <div key={`g${i}`} className="cw-group">
49
- {g.items.map(choice)}
50
- </div>
51
- ) : (
52
- <fieldset key={`g${i}`} className="cw-fieldset cw-group">
53
- <legend className="cw-group-legend">{g.name}</legend>
54
- {g.items.map(choice)}
55
- </fieldset>
56
- ),
57
- )}
58
- </fieldset>
59
- );
60
- }