@remit/ui 0.0.97 → 0.0.98
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
|
@@ -27,6 +27,10 @@ export interface ComposeFormShellProps {
|
|
|
27
27
|
* pinned action bar. Owns the column structure so the action bar always sits
|
|
28
28
|
* at the bottom and never clips below the fold. The live form composes this
|
|
29
29
|
* with its provider-driven slots; stories render it with static slots.
|
|
30
|
+
*
|
|
31
|
+
* The body region is a column so the editor can claim the space the quote and
|
|
32
|
+
* the action bar leave — a body slot shorter than the region would otherwise
|
|
33
|
+
* leave dead, unclickable canvas under it.
|
|
30
34
|
*/
|
|
31
35
|
export function ComposeFormShell({
|
|
32
36
|
banner,
|
|
@@ -39,9 +43,12 @@ export function ComposeFormShell({
|
|
|
39
43
|
<div className="flex h-full min-h-0 flex-col">
|
|
40
44
|
{banner}
|
|
41
45
|
{header}
|
|
42
|
-
<div
|
|
46
|
+
<div
|
|
47
|
+
className="flex min-h-0 flex-1 flex-col overflow-auto"
|
|
48
|
+
data-testid="compose-body-area"
|
|
49
|
+
>
|
|
43
50
|
{children}
|
|
44
|
-
{quoted && <div className="px-3 pb-2">{quoted}</div>}
|
|
51
|
+
{quoted && <div className="shrink-0 px-3 pb-2">{quoted}</div>}
|
|
45
52
|
</div>
|
|
46
53
|
{actionBar}
|
|
47
54
|
</div>
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
2
3
|
import { sanitizeAdoptedHtml } from "../lib/adopted-html.js";
|
|
3
4
|
import { RichTextEditor } from "./rich-text-editor.js";
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The frame is the compose body region at its real geometry — a column with a
|
|
8
|
+
* height of its own — so the editor is shown claiming the space a composer
|
|
9
|
+
* gives it rather than only the space its own text needs.
|
|
10
|
+
*/
|
|
5
11
|
const meta: Meta<typeof RichTextEditor> = {
|
|
6
12
|
title: "Mail/RichTextEditor",
|
|
7
13
|
component: RichTextEditor,
|
|
8
14
|
parameters: { layout: "centered" },
|
|
9
15
|
decorators: [
|
|
10
16
|
(Story) => (
|
|
11
|
-
<div
|
|
17
|
+
<div
|
|
18
|
+
data-testid="body-area"
|
|
19
|
+
className="flex h-[420px] w-[640px] flex-col overflow-auto rounded-md border border-line bg-canvas"
|
|
20
|
+
>
|
|
12
21
|
<Story />
|
|
13
22
|
</div>
|
|
14
23
|
),
|
|
@@ -64,3 +73,32 @@ export const PasteResult: Story = {
|
|
|
64
73
|
name: "After pasting a web page",
|
|
65
74
|
args: { initialHtml: sanitizeAdoptedHtml(CLIPBOARD_HTML) },
|
|
66
75
|
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A short message leaves most of the body region empty. That region belongs to
|
|
79
|
+
* the document: the point far below the last line is the editable, and a click
|
|
80
|
+
* there lands in it.
|
|
81
|
+
*/
|
|
82
|
+
export const ClickBelowTheText: Story = {
|
|
83
|
+
name: "Clicking below the last line",
|
|
84
|
+
args: { initialHtml: "<p>Sounds good. See you at 12:30.</p>" },
|
|
85
|
+
play: async ({ canvasElement }) => {
|
|
86
|
+
const area = canvasElement.querySelector<HTMLElement>(
|
|
87
|
+
"[data-testid=body-area]",
|
|
88
|
+
);
|
|
89
|
+
const editable = canvasElement.querySelector<HTMLElement>(
|
|
90
|
+
"[data-testid=compose-body]",
|
|
91
|
+
);
|
|
92
|
+
if (!area || !editable) throw new Error("the editor is not mounted");
|
|
93
|
+
|
|
94
|
+
const box = area.getBoundingClientRect();
|
|
95
|
+
const underTheText = document.elementFromPoint(
|
|
96
|
+
box.left + box.width / 2,
|
|
97
|
+
Math.min(box.bottom - 12, window.innerHeight - 2),
|
|
98
|
+
);
|
|
99
|
+
await expect(editable.contains(underTheText)).toBe(true);
|
|
100
|
+
|
|
101
|
+
await userEvent.click(editable);
|
|
102
|
+
await expect(editable).toHaveFocus();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -173,31 +173,36 @@ export const RichTextEditor = ({
|
|
|
173
173
|
},
|
|
174
174
|
}}
|
|
175
175
|
>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
event
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
176
|
+
{/* The editable claims the height its container offers rather than only the
|
|
177
|
+
height of its own text. What is under the last line is the document, so
|
|
178
|
+
a click there reaches it instead of an unfocusable parent. */}
|
|
179
|
+
<div className="flex shrink-0 grow flex-col">
|
|
180
|
+
<RichTextToolbar />
|
|
181
|
+
<div className="relative flex shrink-0 grow flex-col">
|
|
182
|
+
<RichTextPlugin
|
|
183
|
+
contentEditable={
|
|
184
|
+
<ContentEditable
|
|
185
|
+
aria-label={ariaLabel}
|
|
186
|
+
aria-placeholder={placeholder}
|
|
187
|
+
data-testid="compose-body"
|
|
188
|
+
className="min-h-[120px] w-full shrink-0 grow bg-canvas px-3 py-2 text-sm text-fg outline-none"
|
|
189
|
+
placeholder={
|
|
190
|
+
<div className="pointer-events-none absolute inset-x-0 top-0 px-3 py-2 text-sm text-fg-subtle">
|
|
191
|
+
{placeholder}
|
|
192
|
+
</div>
|
|
193
|
+
}
|
|
194
|
+
onKeyDown={(event) => {
|
|
195
|
+
if (!onSubmit) return;
|
|
196
|
+
if (!(event.metaKey || event.ctrlKey) || event.key !== "Enter")
|
|
197
|
+
return;
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
onSubmit();
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
}
|
|
203
|
+
ErrorBoundary={LexicalErrorBoundary}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
201
206
|
</div>
|
|
202
207
|
<HistoryPlugin />
|
|
203
208
|
<ListPlugin />
|