fix: the Shape Format group lines up and its menu opens above the toolbar

The shape trigger stood 34px tall next to the 28px colour pickers, so its
caption sat a line below theirs; every captioned picker now shares one 32px
box and the border-width field centres on it. The shape menu was absolute
inside the band, which clips its overflow and stacks below the floating box
toolbar, so it is placed from the button's rect in the top layer, as the
colour picker already does. Its caret is the same chevron as theirs.
This commit is contained in:
Steffen Becker committed 2026-09-04 19:30:04 +02:00
1 parent cdecbee234
commit 71fc8ef942
3 files changed
+30 -12

No files matched your search

+27 -10
View File
@@ -14,6 +14,19 @@
const KINDS = Object.keys(SHAPES) as ShapeKind[];
let open = $state(false);
let host = $state<HTMLElement | null>(null);
let btn = $state<HTMLElement | null>(null);
// The ribbon band clips its overflow and the floating box toolbar stacks above it,
// so the menu is placed from the button's rect and shown in the top layer.
let pos = $state({ top: 0, left: 0 });
const topLayer = (node: HTMLElement) => node.showPopover();
function place() {
const r = btn?.getBoundingClientRect();
if (!r) return;
const band = host?.closest('.ribbon-body')?.getBoundingClientRect();
const left = Math.min(r.left, window.innerWidth - 220);
pos = { top: Math.max(r.bottom, band?.bottom ?? 0) + 4, left: Math.max(8, left) };
}
const label = (k: ShapeKind) => t().textBox.shapes[k];
@@ -32,14 +45,17 @@
aria-label={t().textBox.chooseShape}
aria-haspopup="true"
aria-expanded={open}
onclick={() => (open = !open)}
bind:this={btn}
onclick={() => { open = !open; if (open) place(); }}
>
{@render tile(value)}
<span class="chevron" aria-hidden="true">▾</span>
<svg class="chevron" width="8" height="8" viewBox="0 0 8 8" fill="none" aria-hidden="true">
<path d="M1 2.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
{#if open}
<div class="shape-menu" role="menu">
<div class="shape-menu" role="menu" popover="manual" use:topLayer style="top: {pos.top}px; left: {pos.left}px">
{#each KINDS as k}
<button
class="shape-tile"
@@ -93,15 +109,16 @@
cursor: pointer;
}
.shape-btn:hover { background: var(--w-hover, rgba(0, 0, 0, 0.08)); }
.shape-btn svg { width: 22px; height: 22px; }
.shape-btn.compact svg { width: 18px; height: 18px; }
.chevron { font-size: 9px; line-height: 1; }
/* Kept below the ribbon's own 28px rule for a captioned picker, so the tile grows
there and stays compact in the floating toolbar. */
:where(.shape-btn) svg:first-of-type { width: 22px; height: 22px; }
:where(.shape-btn.compact) svg:first-of-type { width: 18px; height: 18px; }
.chevron { width: 8px; height: 8px; flex: none; }
.shape-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
z-index: 60;
position: fixed;
inset: auto;
margin: 0;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 2px;
@@ -170,6 +170,7 @@
.field {
display: flex;
align-items: center;
align-self: center;
gap: 6px;
color: var(--w-text-dim);
}
+2 -2
View File
@@ -469,7 +469,7 @@
padding: 2px 6px 3px;
border-radius: 4px;
cursor: pointer;
--toolbar-btn-size: 28px;
--toolbar-btn-size: 32px;
}
/* Mirrors .rb-big's box, down to the slot it holds open for a caret, so a picker
@@ -479,7 +479,7 @@
baseline and carry the descender gap with it. */
.rb-captioned > * { display: flex; }
.rb-captioned :is(.table-trigger, .char-trigger, .dt-trigger, .tsp-trigger, .bp-trigger, .lang-picker) {
.rb-captioned :is(.table-trigger, .char-trigger, .dt-trigger, .tsp-trigger, .bp-trigger, .lang-picker, .shape-btn) {
height: 32px;
min-height: 32px;
padding: 0;