Skip to content

Commit 8c1045f

Browse files
committed
fix(ui): shrink the floating controls to 28px and drop the image block margin
The toolbars, link card and input popover used 32px controls with 18px icons, taller than the 24px lines they float over and larger than a host app's own controls. Every .mbe-tool is now a 28px control with a 16px icon, so a bar is 36px tall with its 4px padding, and the popover input matches. An image kept its 8px inline margin when alone in its block, which stacked on the block gap and left it 16px from the text around it. An image block now has no margin; inline images keep theirs.
1 parent bc6e1f6 commit 8c1045f

5 files changed

Lines changed: 51 additions & 11 deletions

File tree

‎e2e/image.spec.ts‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,24 @@ test('the selected outline is drawn inside the image', async ({ page }) => {
198198
expect(await image(page).evaluate((el) => getComputedStyle(el).outlineOffset)).toBe('-2px');
199199
});
200200

201+
test('an image block is as tall as its image and one block gap from the text around it', async ({ page }) => {
202+
await open(page, 'above\n\n![chart|200](/img/400x200.svg)\n\nbelow\n');
203+
const boxes = async () => {
204+
const [above, block, below] = await page.locator('#editor > p').evaluateAll((els) => els.map((el) => el.getBoundingClientRect()));
205+
const img = (await image(page).boundingBox())!;
206+
return { block: block!.height, image: img.height, gapAbove: block!.top - above!.bottom, gapBelow: below!.top - block!.bottom };
207+
};
208+
expect(await boxes()).toEqual({ block: 100, image: 100, gapAbove: 8, gapBelow: 8 });
209+
// Selecting the image changes nothing: the resize frame covers exactly the image.
210+
await image(page).click();
211+
await expect(resizeHandle(page, 'right')).toBeVisible();
212+
expect(await boxes()).toEqual({ block: 100, image: 100, gapAbove: 8, gapBelow: 8 });
213+
expect(await page.locator('.mbe-image-frame').boundingBox()).toEqual(await image(page).boundingBox());
214+
// An inline image keeps its room between the lines of text.
215+
await open(page, 'a ![inline](/img/100x50.svg) b\n');
216+
expect((await page.locator('#editor > p').boundingBox())!.height).toBe(50 + 16);
217+
});
218+
201219
test('updateImage changes the selected image', async ({ page }) => {
202220
await open(page, '![chart|120x60](/img/400x200.svg)\n');
203221
await image(page).click();
@@ -433,11 +451,13 @@ test('clicking an image shows the image toolbar: alignment on an image block, al
433451
for (const name of ['Align left', 'Align center', 'Align right', 'Alt text', 'Reset size', 'Delete']) await expect(tool(page, name)).toBeVisible();
434452
await expect(tool(page, 'Align left')).toHaveAttribute('aria-pressed', 'true');
435453
await expect(tool(page, 'Align center')).toHaveAttribute('aria-pressed', 'false');
436-
// Above the image, starting at its left edge.
454+
// 8px above the image, starting at its left edge; the same size as the formatting toolbar.
437455
const box = (await image(page).boundingBox())!;
438456
const bar = (await imageToolbar(page).boundingBox())!;
439-
expect(bar.y + bar.height).toBeLessThanOrEqual(box.y);
457+
expect(bar.y + bar.height).toBe(box.y - 8);
440458
expect(bar.x).toBeCloseTo(box.x, 0);
459+
expect(bar.height).toBe(38);
460+
expect(await tool(page, 'Delete').boundingBox()).toMatchObject({ width: 28, height: 28 });
441461
// An inline image has no alignment and no width to reset.
442462
await image(page, 1).click();
443463
await expect(imageToolbar(page)).toBeVisible();

‎e2e/link-card.spec.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ test('resting the pointer on a link shows its card with the URL; leaving hides i
1717
const linkBox = (await page.locator('#editor a.mbe-link').boundingBox())!;
1818
const cardBox = (await card(page).boundingBox())!;
1919
expect(cardBox.y).toBeGreaterThanOrEqual(linkBox.y + linkBox.height);
20+
// The same compact controls as the toolbars.
21+
expect(cardBox.height).toBe(38);
22+
expect(await card(page).locator('button').first().boundingBox()).toMatchObject({ width: 28, height: 28 });
2023
// Moving from the link onto the card keeps it; moving away hides it.
2124
await card(page).hover();
2225
await expect(card(page)).toBeVisible();

‎e2e/ui.spec.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ test('formatting toolbar: nine controls in three groups, named without shortcuts
130130
['Code block', 1],
131131
['List', 2],
132132
]);
133+
// Compact next to the 24px lines it floats over: 28px controls with 16px icons in 4px of padding,
134+
// 36px plus the theme's 1px border.
135+
const sizes = await toolbar.evaluate((el) => {
136+
const size = (node: Element) => [node.getBoundingClientRect().width, node.getBoundingClientRect().height];
137+
return { bar: size(el)[1], tool: size(el.querySelector('.mbe-tool-bold')!), icon: size(el.querySelector('.mbe-tool-bold .mbe-icon')!) };
138+
});
139+
expect(sizes).toEqual({ bar: 38, tool: [28, 28], icon: [16, 16] });
133140
// No shortcut is shown anywhere; assistive technology gets it from aria-keyshortcuts.
134141
await toolbar.locator('.mbe-tool-text-style').click();
135142
const text = await page.evaluate(() =>
@@ -337,6 +344,9 @@ test('link: Ctrl+K on a selection, then edit and remove', async ({ page }) => {
337344
await page.keyboard.press('Control+k');
338345
const input = page.locator('.mbe-popover-input');
339346
await expect(input).toBeFocused();
347+
// The popover is the size of the toolbars: a 28px input in 4px of padding and the theme's border.
348+
expect(await page.locator('.mbe-input-popover').boundingBox()).toMatchObject({ height: 38 });
349+
expect(await input.boundingBox()).toMatchObject({ height: 28 });
340350
await input.fill('https://example.com');
341351
await input.press('Enter');
342352
expect(await md(page)).toBe('see [docs](https://example.com)\n');

‎packages/editor/src/base.css‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@
180180
.mbe-root.mbe-readonly a.mbe-link { cursor: pointer; }
181181
/* Inline even under a CSS reset such as Tailwind's preflight, which makes images blocks; alignment needs it. */
182182
.mbe-root .mbe-image { display: inline-block; max-width: 100%; height: auto; margin: 8px 0; vertical-align: bottom; }
183+
/* An inline image gets room between the lines of text around it. An image
184+
alone in its block does not: the block gap already separates it, and the
185+
margin would make its block 16px taller than the image. */
186+
.mbe-root .mbe-image-block .mbe-image { margin: 0; }
183187
/* An image alone in its paragraph moves within the column; it keeps its width. */
184188
.mbe-root .mbe-image-block[data-align="left"] { text-align: left; }
185189
.mbe-root .mbe-image-block[data-align="center"] { text-align: center; }
@@ -353,29 +357,32 @@
353357
.mbe-image-toolbar { box-sizing: border-box; display: flex; align-items: center; gap: 2px; padding: 4px; overflow-x: auto; scrollbar-width: none; }
354358
.mbe-toolbar::-webkit-scrollbar,
355359
.mbe-image-toolbar::-webkit-scrollbar { display: none; }
360+
/* A tool is a 28px control with a 16px icon, so a bar is 36px tall with its
361+
4px padding: compact next to the 24px lines it floats over, and the size
362+
of a host app's own controls. */
356363
.mbe-tool {
357364
flex: none;
358365
font: inherit;
359366
color: inherit;
360367
background: none;
361368
border: none;
362-
padding: 4px 7px;
363-
min-width: 32px;
364-
min-height: 32px;
369+
padding: 2px 6px;
370+
min-width: 28px;
371+
min-height: 28px;
365372
display: inline-flex;
366373
align-items: center;
367374
justify-content: center;
368375
cursor: pointer;
369376
}
370377
.mbe-tool[hidden],
371378
.mbe-tool-sep[hidden] { display: none; }
372-
.mbe-tool .mbe-icon { width: 18px; height: 18px; }
373-
.mbe-tool-menu { gap: 1px; padding-inline: 7px 4px; }
379+
.mbe-tool .mbe-icon { width: 16px; height: 16px; }
380+
.mbe-tool-menu { gap: 1px; padding-inline: 6px 3px; }
374381
.mbe-tool-icon,
375382
.mbe-tool-chevron { display: inline-flex; }
376-
.mbe-tool .mbe-tool-chevron .mbe-icon { width: 13px; height: 13px; }
383+
.mbe-tool .mbe-tool-chevron .mbe-icon { width: 12px; height: 12px; }
377384
.mbe-tool.mbe-active { background: Highlight; color: HighlightText; }
378-
.mbe-tool-sep { flex: none; width: 1px; height: 18px; margin: 0 3px; background: GrayText; }
385+
.mbe-tool-sep { flex: none; width: 1px; height: 16px; margin: 0 2px; background: GrayText; }
379386
.mbe-menu-check { flex: none; display: inline-flex; margin-inline-start: auto; }
380387
.mbe-menu-submenu { flex: none; display: inline-flex; margin-inline-start: auto; }
381388
.mbe-menu-submenu .mbe-icon { width: 14px; height: 14px; }
@@ -417,7 +424,7 @@
417424
.mbe-image-frame-small .mbe-resize-handle-right { left: 100%; }
418425
.mbe-image-resizing,
419426
.mbe-image-resizing .mbe-resize-handle { cursor: ew-resize; }
420-
.mbe-popover-input { font: inherit; color: inherit; width: 260px; padding: 5px 8px; }
427+
.mbe-popover-input { font: inherit; color: inherit; box-sizing: border-box; height: 28px; width: 260px; padding: 0 8px; }
421428

422429
.mbe-block-handle {
423430
position: fixed;

‎packages/editor/src/theme.css‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@
283283
.mbe-tool-delete:hover { color: var(--mbe-danger); }
284284
.mbe-link-card-url { font-size: 13px; color: var(--mbe-link); text-decoration: none; }
285285
.mbe-link-card-url:hover { text-decoration: underline; }
286-
.mbe-tool { border-radius: 7px; }
286+
.mbe-tool { border-radius: 6px; }
287287
.mbe-tool:hover,
288288
.mbe-tool.mbe-open { background: var(--mbe-hover); }
289289
.mbe-tool.mbe-active { color: var(--mbe-accent); background: var(--mbe-hover); }

0 commit comments

Comments
 (0)