@remit/ui 0.0.138 → 0.0.139
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/package.json +1 -1
- package/src/components/folder-manager.tsx +1 -1
- package/src/components/folder-row.render.test.ts +14 -0
- package/src/components/folder-row.tsx +15 -0
- package/src/components/folder-tree-picker.render.test.ts +13 -1
- package/src/components/folder-tree-picker.tsx +9 -4
- package/src/components/role-appointment-list.render.test.ts +126 -6
- package/src/components/role-appointment-list.stories.tsx +103 -21
- package/src/components/role-appointment-list.tsx +123 -23
- package/src/components/role-appointment-prompt.render.test.ts +240 -0
- package/src/components/role-appointment-prompt.stories.tsx +172 -0
- package/src/components/role-appointment-prompt.tsx +309 -0
- package/src/components/select.tsx +11 -2
- package/src/index.ts +11 -0
- package/src/lib/folder-tree.ts +6 -0
package/package.json
CHANGED
|
@@ -81,6 +81,20 @@ describe("FolderRow", () => {
|
|
|
81
81
|
assert.ok(html.indexOf(">Delete<") > html.indexOf("</button>"));
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
it("shows how much mail the folder holds, and leaves zero visible", () => {
|
|
85
|
+
assert.match(render({ messageCount: 512 }), />512</);
|
|
86
|
+
assert.match(render({ messageCount: 512 }), />msgs</);
|
|
87
|
+
assert.match(render({ messageCount: 1 }), />msg</);
|
|
88
|
+
assert.match(render({ messageCount: 0 }), />0</);
|
|
89
|
+
assert.doesNotMatch(render({}), /msgs/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps the count out of the accessible name it was not folded into", () => {
|
|
93
|
+
const html = render({ messageCount: 512 });
|
|
94
|
+
assert.match(html, /aria-label="Move to Travel"/);
|
|
95
|
+
assert.match(html, /aria-hidden="true"[^>]*class="[^"]*text-2xs/);
|
|
96
|
+
});
|
|
97
|
+
|
|
84
98
|
it("takes its place in a roving tab order", () => {
|
|
85
99
|
assert.match(render({ tabIndex: 0 }), /tabindex="0"/);
|
|
86
100
|
assert.match(render({ tabIndex: -1 }), /tabindex="-1"/);
|
|
@@ -50,6 +50,12 @@ export interface FolderRowProps {
|
|
|
50
50
|
current?: boolean;
|
|
51
51
|
/** Inline tag shown on the current row, e.g. `current`. */
|
|
52
52
|
currentTag?: string;
|
|
53
|
+
/**
|
|
54
|
+
* How much mail the folder holds, right-aligned. The accessible name is the
|
|
55
|
+
* caller's `ariaLabel`, so a surface that needs the count announced folds it
|
|
56
|
+
* in there rather than relying on this.
|
|
57
|
+
*/
|
|
58
|
+
messageCount?: number;
|
|
53
59
|
/** A hairline under the label, drawn for every row but the last. */
|
|
54
60
|
separated?: boolean;
|
|
55
61
|
/**
|
|
@@ -77,6 +83,7 @@ export const FolderRow = ({
|
|
|
77
83
|
context = false,
|
|
78
84
|
current = false,
|
|
79
85
|
currentTag,
|
|
86
|
+
messageCount,
|
|
80
87
|
separated = false,
|
|
81
88
|
actions,
|
|
82
89
|
tabIndex,
|
|
@@ -96,6 +103,12 @@ export const FolderRow = ({
|
|
|
96
103
|
const icon = (
|
|
97
104
|
<Folder className="size-4 shrink-0 text-fg-subtle" aria-hidden="true" />
|
|
98
105
|
);
|
|
106
|
+
const count =
|
|
107
|
+
messageCount === undefined ? null : (
|
|
108
|
+
<span aria-hidden="true" className="shrink-0 text-2xs text-fg-subtle">
|
|
109
|
+
{messageCount} {messageCount === 1 ? "msg" : "msgs"}
|
|
110
|
+
</span>
|
|
111
|
+
);
|
|
99
112
|
if (context) {
|
|
100
113
|
return (
|
|
101
114
|
<div className="relative flex items-center">
|
|
@@ -112,6 +125,7 @@ export const FolderRow = ({
|
|
|
112
125
|
{chevron}
|
|
113
126
|
{icon}
|
|
114
127
|
<span className="min-w-0 flex-1 truncate">{label}</span>
|
|
128
|
+
{count}
|
|
115
129
|
</div>
|
|
116
130
|
{actions}
|
|
117
131
|
{separated && <FolderRowSeparator depth={depth} />}
|
|
@@ -142,6 +156,7 @@ export const FolderRow = ({
|
|
|
142
156
|
{chevron}
|
|
143
157
|
{icon}
|
|
144
158
|
<span className="min-w-0 flex-1 truncate">{label}</span>
|
|
159
|
+
{count}
|
|
145
160
|
{current && currentTag && (
|
|
146
161
|
<span className="shrink-0 text-xs text-fg-muted">{currentTag}</span>
|
|
147
162
|
)}
|
|
@@ -101,8 +101,20 @@ describe("FolderTreePicker render", () => {
|
|
|
101
101
|
|
|
102
102
|
it("applies caller-supplied labels", () => {
|
|
103
103
|
const html = render({
|
|
104
|
-
labels: { optionLabel: (
|
|
104
|
+
labels: { optionLabel: (folder) => `Verplaats naar ${folder.label}` },
|
|
105
105
|
});
|
|
106
106
|
assert.match(html, /aria-label="Verplaats naar Archive"/);
|
|
107
107
|
});
|
|
108
|
+
|
|
109
|
+
it("announces the message count as part of the row's own name", () => {
|
|
110
|
+
const html = render({
|
|
111
|
+
folders: [{ id: "t", label: "Trash", path: "Trash", messageCount: 512 }],
|
|
112
|
+
labels: {
|
|
113
|
+
optionLabel: (folder) =>
|
|
114
|
+
`Set ${folder.label}, ${folder.messageCount} messages, as Trash`,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
assert.match(html, /aria-label="Set Trash, 512 messages, as Trash"/);
|
|
118
|
+
assert.match(html, />512</);
|
|
119
|
+
});
|
|
108
120
|
});
|
|
@@ -55,8 +55,12 @@ export interface FolderTreePickerLabels {
|
|
|
55
55
|
emptyMessage?: (query: string) => string;
|
|
56
56
|
/** Shown when there is no folder to list at all, filter or no filter. */
|
|
57
57
|
noFolders?: string;
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Accessible label for a selectable row. Takes the folder rather than its
|
|
60
|
+
* label, so a surface can announce the message count as part of the name
|
|
61
|
+
* instead of leaving it as decoration beside it.
|
|
62
|
+
*/
|
|
63
|
+
optionLabel?: (folder: FolderTreeNode) => string;
|
|
60
64
|
newFolder?: string;
|
|
61
65
|
newSubfolder?: (label: string) => string;
|
|
62
66
|
nameLabel?: string;
|
|
@@ -113,7 +117,7 @@ const defaultLabels: Required<FolderTreePickerLabels> = {
|
|
|
113
117
|
contextSuffix: "(containing folder)",
|
|
114
118
|
emptyMessage: (query) => `No folders match "${query}"`,
|
|
115
119
|
noFolders: "No folders to show",
|
|
116
|
-
optionLabel: (
|
|
120
|
+
optionLabel: (folder) => `Move to ${folder.label}`,
|
|
117
121
|
newFolder: "New folder",
|
|
118
122
|
newSubfolder: (label) => `New folder inside ${label}`,
|
|
119
123
|
topLevel: "Top level",
|
|
@@ -376,7 +380,7 @@ export const FolderTreePicker = ({
|
|
|
376
380
|
|
|
377
381
|
const rowAriaLabel = (row: FolderTreeRow): string => {
|
|
378
382
|
if (row.context) return `${row.folder.label} ${text.contextSuffix}`;
|
|
379
|
-
if (isSelectable(row)) return text.optionLabel(row.folder
|
|
383
|
+
if (isSelectable(row)) return text.optionLabel(row.folder);
|
|
380
384
|
return `${row.folder.label} ${text.currentSuffix}`;
|
|
381
385
|
};
|
|
382
386
|
|
|
@@ -394,6 +398,7 @@ export const FolderTreePicker = ({
|
|
|
394
398
|
context={row.context}
|
|
395
399
|
current={folder.isCurrent}
|
|
396
400
|
currentTag={text.currentTag}
|
|
401
|
+
messageCount={folder.messageCount}
|
|
397
402
|
selected={selectable && folder.id === selectedId}
|
|
398
403
|
separated={separated}
|
|
399
404
|
actions={rowActions?.(folder)}
|
|
@@ -4,6 +4,7 @@ import { createElement } from "react";
|
|
|
4
4
|
import { renderToString } from "react-dom/server";
|
|
5
5
|
import {
|
|
6
6
|
type CandidateFolder,
|
|
7
|
+
type RoleAppointment,
|
|
7
8
|
RoleAppointmentList,
|
|
8
9
|
} from "./role-appointment-list.js";
|
|
9
10
|
|
|
@@ -24,8 +25,13 @@ const folders: CandidateFolder[] = [
|
|
|
24
25
|
},
|
|
25
26
|
];
|
|
26
27
|
|
|
28
|
+
const appointed = (mailboxId: string): RoleAppointment => ({
|
|
29
|
+
mailboxId,
|
|
30
|
+
source: "Appointed",
|
|
31
|
+
});
|
|
32
|
+
|
|
27
33
|
function render(
|
|
28
|
-
appointments: Record<string,
|
|
34
|
+
appointments: Record<string, RoleAppointment>,
|
|
29
35
|
displayNames: Record<string, string> = {},
|
|
30
36
|
): string {
|
|
31
37
|
return renderToString(
|
|
@@ -42,7 +48,7 @@ function render(
|
|
|
42
48
|
|
|
43
49
|
describe("RoleAppointmentList", () => {
|
|
44
50
|
it("titles the section with the account email", () => {
|
|
45
|
-
const html = render({ inbox: "mb-inbox" });
|
|
51
|
+
const html = render({ inbox: appointed("mb-inbox") });
|
|
46
52
|
assert.match(html, /Folder roles —/);
|
|
47
53
|
assert.match(html, /you@example.com/);
|
|
48
54
|
});
|
|
@@ -69,29 +75,143 @@ describe("RoleAppointmentList", () => {
|
|
|
69
75
|
});
|
|
70
76
|
|
|
71
77
|
it("shows the appointed folder's path and count under the role", () => {
|
|
72
|
-
const html = render({ drafts: "mb-concepten" });
|
|
78
|
+
const html = render({ drafts: appointed("mb-concepten") });
|
|
73
79
|
assert.match(html, /title="INBOX\/Concepten"/);
|
|
74
80
|
assert.match(html, /340/);
|
|
75
81
|
assert.match(html, /messages/);
|
|
76
82
|
});
|
|
77
83
|
|
|
78
84
|
it("renders a rename field only for an appointed role", () => {
|
|
79
|
-
const html = render({ drafts: "mb-concepten" });
|
|
85
|
+
const html = render({ drafts: appointed("mb-concepten") });
|
|
80
86
|
assert.match(html, /Display name for Drafts/);
|
|
81
87
|
assert.doesNotMatch(html, /Display name for Sent/);
|
|
82
88
|
});
|
|
83
89
|
|
|
84
90
|
it("lists unappointed folders under Other folders", () => {
|
|
85
|
-
const html = render({
|
|
91
|
+
const html = render({
|
|
92
|
+
drafts: appointed("mb-concepten"),
|
|
93
|
+
inbox: appointed("mb-inbox"),
|
|
94
|
+
});
|
|
86
95
|
assert.match(html, /Other folders/);
|
|
87
96
|
assert.match(html, /Nieuwsbrieven/);
|
|
88
97
|
assert.match(html, /Drafts/);
|
|
89
98
|
});
|
|
90
99
|
|
|
91
100
|
it("keeps an appointed folder out of the Other folders list", () => {
|
|
92
|
-
const html = render({ drafts: "mb-concepten" });
|
|
101
|
+
const html = render({ drafts: appointed("mb-concepten") });
|
|
93
102
|
const otherIdx = html.indexOf("Other folders");
|
|
94
103
|
assert.ok(otherIdx >= 0);
|
|
95
104
|
assert.doesNotMatch(html.slice(otherIdx), /Concepten/);
|
|
96
105
|
});
|
|
106
|
+
|
|
107
|
+
it("says where each answer came from, in front of the path", () => {
|
|
108
|
+
assert.match(
|
|
109
|
+
render({ drafts: appointed("mb-concepten") }),
|
|
110
|
+
/Chosen by you · INBOX\/Concepten/,
|
|
111
|
+
);
|
|
112
|
+
assert.match(
|
|
113
|
+
render({ drafts: { mailboxId: "mb-concepten", source: "Flagged" } }),
|
|
114
|
+
/The mail server marks it as Drafts · INBOX\/Concepten/,
|
|
115
|
+
);
|
|
116
|
+
assert.match(
|
|
117
|
+
render({ inbox: { mailboxId: "mb-inbox", source: "Reserved" } }),
|
|
118
|
+
/The account's own INBOX · INBOX/,
|
|
119
|
+
);
|
|
120
|
+
assert.match(
|
|
121
|
+
render({ drafts: { mailboxId: "mb-concepten", source: "Proposed" } }),
|
|
122
|
+
/Matched by name, not confirmed · INBOX\/Concepten/,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("announces the provenance with the control that changes it", () => {
|
|
127
|
+
const html = render({ drafts: appointed("mb-concepten") });
|
|
128
|
+
const described = /aria-describedby="([^"]+)"/.exec(html);
|
|
129
|
+
assert.ok(described);
|
|
130
|
+
assert.match(html, new RegExp(`id="${described[1]}"`));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("reads an unfilled role as a decision waiting, not an error", () => {
|
|
134
|
+
const html = render({ archive: { mailboxId: null, source: "None" } });
|
|
135
|
+
assert.match(
|
|
136
|
+
html,
|
|
137
|
+
/Not set — pick the folder this account uses for Archive\.</,
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("says only Trash gates a verb when it is unfilled", () => {
|
|
142
|
+
const html = render({ trash: { mailboxId: null, source: "None" } });
|
|
143
|
+
assert.match(html, /Deleting mail needs one\./);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("calls out a stale Trash with the folder that vanished and its repair", () => {
|
|
147
|
+
const html = render({
|
|
148
|
+
trash: {
|
|
149
|
+
mailboxId: null,
|
|
150
|
+
source: "Stale",
|
|
151
|
+
staleFolderPath: "INBOX/Prullenbak",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
assert.match(
|
|
155
|
+
html,
|
|
156
|
+
/The folder you chose for Trash — INBOX\/Prullenbak — is gone from the mail server\./,
|
|
157
|
+
);
|
|
158
|
+
assert.match(html, /Deleting mail is stopped until you pick another one\./);
|
|
159
|
+
assert.match(html, /Pick a folder/);
|
|
160
|
+
assert.match(html, /role="alert"/);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("names what reader fell back to for a stale non-Trash role", () => {
|
|
164
|
+
const html = render({
|
|
165
|
+
drafts: {
|
|
166
|
+
mailboxId: "mb-drafts",
|
|
167
|
+
source: "Stale",
|
|
168
|
+
staleFolderPath: "INBOX/Concepten",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
assert.match(
|
|
172
|
+
html,
|
|
173
|
+
/The folder you chose for Drafts — INBOX\/Concepten — is gone from the mail server\./,
|
|
174
|
+
);
|
|
175
|
+
assert.match(html, /reader is using Drafts instead\./);
|
|
176
|
+
assert.doesNotMatch(html, /Deleting mail is stopped/);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("drops the name clause when no path was ever recorded", () => {
|
|
180
|
+
const html = render({ trash: { mailboxId: null, source: "Stale" } });
|
|
181
|
+
assert.match(
|
|
182
|
+
html,
|
|
183
|
+
/The folder you chose for Trash is gone from the mail server\./,
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("says nothing about a fallback it cannot name", () => {
|
|
188
|
+
const html = render({
|
|
189
|
+
drafts: {
|
|
190
|
+
mailboxId: null,
|
|
191
|
+
source: "Stale",
|
|
192
|
+
staleFolderPath: "INBOX/Concepten",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
assert.match(html, /is gone from the mail server\.</);
|
|
196
|
+
assert.doesNotMatch(html, /reader is using/);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("announces the stale callout with the control that repairs it", () => {
|
|
200
|
+
const html = render({
|
|
201
|
+
trash: {
|
|
202
|
+
mailboxId: null,
|
|
203
|
+
source: "Stale",
|
|
204
|
+
staleFolderPath: "INBOX/Prullenbak",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const notice = /id="([^"]+)"[^>]*>The folder you chose for Trash/.exec(
|
|
208
|
+
html,
|
|
209
|
+
);
|
|
210
|
+
assert.ok(notice, "the callout carries an id");
|
|
211
|
+
assert.match(
|
|
212
|
+
html,
|
|
213
|
+
new RegExp(`aria-describedby="${notice[1]}"`),
|
|
214
|
+
"the Select that repairs the role points at it",
|
|
215
|
+
);
|
|
216
|
+
});
|
|
97
217
|
});
|
|
@@ -3,6 +3,7 @@ import { useState } from "react";
|
|
|
3
3
|
import type { FolderRole } from "./folder-role.js";
|
|
4
4
|
import {
|
|
5
5
|
type CandidateFolder,
|
|
6
|
+
type RoleAppointment,
|
|
6
7
|
RoleAppointmentList,
|
|
7
8
|
} from "./role-appointment-list.js";
|
|
8
9
|
|
|
@@ -48,26 +49,45 @@ const HOSTNET_FOLDERS: readonly CandidateFolder[] = [
|
|
|
48
49
|
{ mailboxId: "mb-spam", providerPath: "INBOX/Spam", messageCount: 88 },
|
|
49
50
|
];
|
|
50
51
|
|
|
52
|
+
const appointed = (mailboxId: string): RoleAppointment => ({
|
|
53
|
+
mailboxId,
|
|
54
|
+
source: "Appointed",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Everything except the role each story is about, so one row carries the news. */
|
|
58
|
+
const SETTLED: Record<string, RoleAppointment> = {
|
|
59
|
+
inbox: { mailboxId: "mb-inbox", source: "Reserved" },
|
|
60
|
+
drafts: appointed("mb-concepten"),
|
|
61
|
+
sent: appointed("mb-sent-messages"),
|
|
62
|
+
archive: appointed("mb-archive"),
|
|
63
|
+
junk: appointed("mb-spam"),
|
|
64
|
+
trash: appointed("mb-deleted"),
|
|
65
|
+
};
|
|
66
|
+
|
|
51
67
|
function Harness({
|
|
52
68
|
folders,
|
|
53
69
|
initial,
|
|
54
70
|
}: {
|
|
55
71
|
folders: readonly CandidateFolder[];
|
|
56
|
-
initial: Record<string,
|
|
72
|
+
initial: Record<string, RoleAppointment>;
|
|
57
73
|
}) {
|
|
58
74
|
const [appointments, setAppointments] = useState(initial);
|
|
59
75
|
const [displayNames, setDisplayNames] = useState<Record<string, string>>({});
|
|
60
76
|
|
|
61
77
|
const handleAppoint = (role: FolderRole, mailboxId: string | null) => {
|
|
62
78
|
setAppointments((prev) => {
|
|
63
|
-
const next: Record<string,
|
|
79
|
+
const next: Record<string, RoleAppointment> = {
|
|
64
80
|
...prev,
|
|
65
|
-
[role]: mailboxId
|
|
81
|
+
[role]: mailboxId
|
|
82
|
+
? { mailboxId, source: "Appointed" }
|
|
83
|
+
: { mailboxId: null, source: "None" },
|
|
66
84
|
};
|
|
67
85
|
// Exclusivity: appointing a folder to one role clears it from any other.
|
|
68
86
|
if (mailboxId) {
|
|
69
|
-
for (const
|
|
70
|
-
if (
|
|
87
|
+
for (const other of Object.keys(next)) {
|
|
88
|
+
if (other === role) continue;
|
|
89
|
+
if (next[other]?.mailboxId !== mailboxId) continue;
|
|
90
|
+
next[other] = { mailboxId: null, source: "None" };
|
|
71
91
|
}
|
|
72
92
|
}
|
|
73
93
|
return next;
|
|
@@ -105,34 +125,96 @@ type Story = StoryObj<typeof Harness>;
|
|
|
105
125
|
* The empty look-alikes drop to "Other folders".
|
|
106
126
|
*/
|
|
107
127
|
export const Hostnet: Story = {
|
|
128
|
+
args: { folders: HOSTNET_FOLDERS, initial: SETTLED },
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Flag-first proposal before the user corrects it: detection appointed the
|
|
133
|
+
* `\Drafts`-flagged but empty `INBOX/Drafts` (0) and the empty `INBOX/Sent`.
|
|
134
|
+
* The picker counts reveal the real folders so the user can re-appoint.
|
|
135
|
+
*/
|
|
136
|
+
export const ProposedDefaults: Story = {
|
|
108
137
|
args: {
|
|
109
138
|
folders: HOSTNET_FOLDERS,
|
|
110
139
|
initial: {
|
|
111
|
-
|
|
112
|
-
drafts: "mb-
|
|
113
|
-
sent: "mb-sent
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
...SETTLED,
|
|
141
|
+
drafts: { mailboxId: "mb-drafts", source: "Flagged" },
|
|
142
|
+
sent: { mailboxId: "mb-sent", source: "Flagged" },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** `Appointed` — a person decided, and the row says so. */
|
|
148
|
+
export const AppointedSource: Story = {
|
|
149
|
+
name: "appointed",
|
|
150
|
+
args: { folders: HOSTNET_FOLDERS, initial: SETTLED },
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** `Flagged` — the mail server's own SPECIAL-USE flag, not a guess. */
|
|
154
|
+
export const FlaggedSource: Story = {
|
|
155
|
+
name: "flagged",
|
|
156
|
+
args: {
|
|
157
|
+
folders: HOSTNET_FOLDERS,
|
|
158
|
+
initial: {
|
|
159
|
+
...SETTLED,
|
|
160
|
+
trash: { mailboxId: "mb-deleted", source: "Flagged" },
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** `Reserved` — INBOX is the one role the protocol names for us. */
|
|
166
|
+
export const ReservedSource: Story = {
|
|
167
|
+
name: "reserved",
|
|
168
|
+
args: {
|
|
169
|
+
folders: HOSTNET_FOLDERS,
|
|
170
|
+
initial: {
|
|
171
|
+
...SETTLED,
|
|
172
|
+
inbox: { mailboxId: "mb-inbox", source: "Reserved" },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/** `Proposed` — a name matched. Nobody confirmed it, and the row does not pretend otherwise. */
|
|
178
|
+
export const ProposedSource: Story = {
|
|
179
|
+
name: "proposed",
|
|
180
|
+
args: {
|
|
181
|
+
folders: HOSTNET_FOLDERS,
|
|
182
|
+
initial: {
|
|
183
|
+
...SETTLED,
|
|
184
|
+
trash: { mailboxId: "mb-deleted", source: "Proposed" },
|
|
117
185
|
},
|
|
118
186
|
},
|
|
119
187
|
};
|
|
120
188
|
|
|
121
189
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
190
|
+
* `Stale` — the folder the user chose is gone from the mail server. The one row
|
|
191
|
+
* representing a broken decision, so it is a callout with its repair rather
|
|
192
|
+
* than a subtitle. Deleting mail is stopped until Trash is repaired.
|
|
125
193
|
*/
|
|
126
|
-
export const
|
|
194
|
+
export const StaleSource: Story = {
|
|
195
|
+
name: "stale",
|
|
196
|
+
args: {
|
|
197
|
+
folders: HOSTNET_FOLDERS,
|
|
198
|
+
initial: {
|
|
199
|
+
...SETTLED,
|
|
200
|
+
trash: {
|
|
201
|
+
mailboxId: null,
|
|
202
|
+
source: "Stale",
|
|
203
|
+
staleFolderPath: "INBOX/Prullenbak",
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** `None` — a decision waiting to be made. No icon, no danger colour. */
|
|
210
|
+
export const NoneSource: Story = {
|
|
211
|
+
name: "none",
|
|
127
212
|
args: {
|
|
128
213
|
folders: HOSTNET_FOLDERS,
|
|
129
214
|
initial: {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
archive: "mb-archive",
|
|
134
|
-
junk: "mb-spam",
|
|
135
|
-
trash: "mb-deleted",
|
|
215
|
+
...SETTLED,
|
|
216
|
+
trash: { mailboxId: null, source: "None" },
|
|
217
|
+
archive: { mailboxId: null, source: "None" },
|
|
136
218
|
},
|
|
137
219
|
},
|
|
138
220
|
};
|