@remit/ui 0.0.28 → 0.0.30
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
CHANGED
|
@@ -107,7 +107,15 @@ export const MessageBodyView = ({
|
|
|
107
107
|
|
|
108
108
|
return (
|
|
109
109
|
<div className={cn("message-body", className)}>
|
|
110
|
-
{
|
|
110
|
+
{/* `message-body-notice` is a styling hook, not a style: a container
|
|
111
|
+
that drops the body's horizontal inset (the mobile reading pane)
|
|
112
|
+
uses it to give the gutter back to this notice, which is app
|
|
113
|
+
chrome rather than part of the email. */}
|
|
114
|
+
{blockedImageCount > 0 && (
|
|
115
|
+
<div className="message-body-notice">
|
|
116
|
+
{renderBlockedNotice?.(blockedImageCount)}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
111
119
|
|
|
112
120
|
{sanitizedHtml ? (
|
|
113
121
|
// Email HTML renders inside a sandboxed iframe so its own CSS and any
|
|
@@ -232,14 +232,21 @@ export function ExpandedMessage({
|
|
|
232
232
|
exactly what the app renders, not a divergent inline-HTML mock
|
|
233
233
|
(#940). `framed` fixtures map to the newsletter treatment (author
|
|
234
234
|
colors preserved); the rest render plain. */}
|
|
235
|
-
{
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
{/* On a phone the email runs edge to edge: the negative margin cancels
|
|
236
|
+
this block's `px-2` so no app canvas shows beside the body and the
|
|
237
|
+
email doesn't read as sitting in a tinted frame (#763). The header
|
|
238
|
+
keeps its inset. The blocked-images notice is app chrome, not part
|
|
239
|
+
of the email, so it takes the gutter back. Desktop is unchanged. */}
|
|
240
|
+
<div className="-mx-2 [&_.message-body-notice]:px-2 lg:mx-0 lg:[&_.message-body-notice]:px-0">
|
|
241
|
+
{body ?? (
|
|
242
|
+
<MessageBodyView
|
|
243
|
+
className="mt-3"
|
|
244
|
+
html={message.bodyHtml}
|
|
245
|
+
category={message.framed ? "newsletter" : "personal"}
|
|
246
|
+
allowImages
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
243
250
|
</div>
|
|
244
251
|
);
|
|
245
252
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { FolderInput } from "lucide-react";
|
|
2
3
|
import { SelectionTopBar } from "./selection-top-bar.js";
|
|
3
4
|
|
|
4
5
|
const meta: Meta<typeof SelectionTopBar> = {
|
|
@@ -22,6 +23,18 @@ export default meta;
|
|
|
22
23
|
|
|
23
24
|
type Story = StoryObj<typeof SelectionTopBar>;
|
|
24
25
|
|
|
26
|
+
/** Stand-in for the caller's move-to-folder trigger, which owns its own
|
|
27
|
+
* folder-picker data. The bar only reserves the slot. */
|
|
28
|
+
const MoveSlot = () => (
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
aria-label="Move selected messages"
|
|
32
|
+
className="inline-flex size-11 shrink-0 items-center justify-center rounded text-fg-muted hover:bg-surface-raised"
|
|
33
|
+
>
|
|
34
|
+
<FolderInput className="size-4" />
|
|
35
|
+
</button>
|
|
36
|
+
);
|
|
37
|
+
|
|
25
38
|
export const One: Story = { args: { count: 1 } };
|
|
26
39
|
|
|
27
40
|
export const Many: Story = { args: { count: 3 } };
|
|
@@ -75,7 +88,7 @@ export const AllSelected: Story = {
|
|
|
75
88
|
/**
|
|
76
89
|
* The search has more matches than are loaded: an escalation notice offers a
|
|
77
90
|
* real button (not prose) naming the scope. Tapping it is what flips the
|
|
78
|
-
* selection's identity from an id set to the search query (`
|
|
91
|
+
* selection's identity from an id set to the search query (`useEscalatedActions`
|
|
79
92
|
* in web-client). No count in the label yet — the real client's own read path
|
|
80
93
|
* (`ThreadOperations.searchThreads`) only counts within a capped recency
|
|
81
94
|
* window short of paging the whole result set, and paging it just to seed a
|
|
@@ -107,11 +120,19 @@ export const EscalationAvailable: Story = {
|
|
|
107
120
|
* Selection has been escalated to the search query: the count names the
|
|
108
121
|
* query's total, not a materialized id count, and the notice offers a way
|
|
109
122
|
* back to the bounded selection.
|
|
123
|
+
*
|
|
124
|
+
* Every verb the bar carries stays available here (#114). An escalated
|
|
125
|
+
* selection is a predicate rather than an id list, so the web-client runs
|
|
126
|
+
* move and mark-read by paging that predicate the same way delete does —
|
|
127
|
+
* from the bar's side nothing changes, which is the point: an escalated
|
|
128
|
+
* selection that could only be deleted forced anyone wanting to file those
|
|
129
|
+
* messages back to the loaded page.
|
|
110
130
|
*/
|
|
111
131
|
export const Escalated: Story = {
|
|
112
132
|
args: {
|
|
113
133
|
count: 3412,
|
|
114
134
|
statusLabel: 'All 3,412 matching "npm" selected',
|
|
135
|
+
moveSlot: <MoveSlot />,
|
|
115
136
|
notice: {
|
|
116
137
|
tone: "info",
|
|
117
138
|
text: "",
|
|
@@ -199,3 +220,44 @@ export const PartialFailure: Story = {
|
|
|
199
220
|
export const ZeroSelected: Story = {
|
|
200
221
|
args: { count: 0 },
|
|
201
222
|
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A move over an escalated selection: same chunked run as a delete, worded for
|
|
226
|
+
* the action that is running and toned as ordinary progress rather than
|
|
227
|
+
* destructive. Mark-read and the move slot are hidden while it runs — nothing
|
|
228
|
+
* here can act mid-run.
|
|
229
|
+
*/
|
|
230
|
+
export const MovingWithProgress: Story = {
|
|
231
|
+
args: {
|
|
232
|
+
count: 3412,
|
|
233
|
+
statusLabel: "Moving 1,200 of 3,412…",
|
|
234
|
+
isBusy: true,
|
|
235
|
+
progress: { value: 1200, max: 3412, tone: "info" },
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/** Mark-read over the same escalated selection. */
|
|
240
|
+
export const MarkingReadWithProgress: Story = {
|
|
241
|
+
args: {
|
|
242
|
+
count: 3412,
|
|
243
|
+
statusLabel: "Marking 1,200 of 3,412 as read…",
|
|
244
|
+
isBusy: true,
|
|
245
|
+
progress: { value: 1200, max: 3412, tone: "info" },
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Partial failure of a move rather than a delete: the notice names the action
|
|
251
|
+
* that ran, and Retry resends that same action against what is still selected.
|
|
252
|
+
*/
|
|
253
|
+
export const PartialFailureMove: Story = {
|
|
254
|
+
args: {
|
|
255
|
+
count: 340,
|
|
256
|
+
moveSlot: <MoveSlot />,
|
|
257
|
+
notice: {
|
|
258
|
+
tone: "danger",
|
|
259
|
+
text: "3,072 moved. 340 couldn't be moved.",
|
|
260
|
+
action: { label: "Retry 340", onClick: () => undefined },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
};
|