fix(sidebar): en gruppe står nu hvor projekterne står, og hedder hele sit navn
To fejl i samme række. "Omdøb" og "Slet" lå i flowet og optog deres bredde også usynlige, så ARBEJDE blev tegnet som "ARBE"; de ligger nu over rækkens højre ende og dukker op ved hover. Og chevronen sad før navnet og skubbede en gruppe toogtyve pixels længere ind end de projekter, den indeholder — så en gruppe så ud til at ligge under dem. Den deler nu plads med gruppens prik: prikken hviler, chevronen tager over ved hover, og bliver stående mens gruppen er foldet. De sidste otte pixels sad i a-reglen, som giver hvert link i sidebjælken sin egen venstrepolstring oven i rækkens. Prøven, der holdt øje med rækken, målte at alt lå side om side — hvilket var netop den geometri, der skulle laves om. Den måler nu det, der blev bedt om: at navnet kan læses helt, og at gruppens prik står præcis hvor et projekts gør. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dab1827c14
commit
bb25b94f96
+34
-19
@@ -302,26 +302,41 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me
|
||||
await expect(heading).toBeVisible();
|
||||
await expect(sidebar.getByText('Træk et projekt herop')).toBeVisible();
|
||||
|
||||
// The words in the heading must not sit on top of each other.
|
||||
// A group's name must be readable in full, and must start where a project's
|
||||
// name starts.
|
||||
//
|
||||
// Everything else here clicks by accessible name, which is exactly why this
|
||||
// needs saying out loud: "Omdøb" and "Slet" once got the 20px square meant for
|
||||
// the "+" icon beside "Projekter", overflowed it, and were drawn overlapping.
|
||||
// Both buttons still worked, so every assertion in this file passed.
|
||||
const boxes = await sidebar.locator('.folder-head').evaluate((el) =>
|
||||
// Links as well as buttons: the group's name is a link to its page now, and
|
||||
// a measurement that skipped it would miss the widest thing in the row.
|
||||
[...el.querySelectorAll('button, a')].map((b) => {
|
||||
const r = b.getBoundingClientRect();
|
||||
return { text: b.textContent.trim(), left: r.left, right: r.right };
|
||||
})
|
||||
);
|
||||
for (let i = 1; i < boxes.length; i++) {
|
||||
expect(
|
||||
boxes[i].left,
|
||||
`"${boxes[i - 1].text}" og "${boxes[i].text}" overlapper i gruppehovedet`
|
||||
).toBeGreaterThanOrEqual(boxes[i - 1].right);
|
||||
}
|
||||
// Both halves are here because both were once broken at the same time and for
|
||||
// different reasons. "Omdøb" and "Slet" sat in the flow reserving their width
|
||||
// even while invisible, so ARBEJDE was drawn as "ARBE"; and the fold chevron
|
||||
// sat before the name, pushing a group 22px further in than the projects it
|
||||
// contains — so a group read as if it were filed under them. Every assertion
|
||||
// in this file passed through both, because both buttons still worked.
|
||||
const shape = await sidebar.locator('.folder-head').evaluate((el) => {
|
||||
const link = el.querySelector('h2 a');
|
||||
const name = { ...link.querySelector('.dot').getBoundingClientRect().toJSON() };
|
||||
// The last child of the link is the name itself; the dot is a span before it.
|
||||
return {
|
||||
clipped: link.scrollWidth > link.clientWidth + 1,
|
||||
left: name.left,
|
||||
right: link.getBoundingClientRect().right,
|
||||
count: el.querySelector('.count')?.getBoundingClientRect().left ?? Infinity
|
||||
};
|
||||
});
|
||||
|
||||
expect(shape.clipped, 'gruppens navn bliver klippet').toBe(false);
|
||||
expect(shape.count, 'navnet og tallet overlapper').toBeGreaterThanOrEqual(shape.right);
|
||||
|
||||
// Measured dot to dot, not box to box: both rows begin with the same coloured
|
||||
// mark, and it is the only thing in them that means the same on both sides.
|
||||
// Comparing the group's inner link with the project's outer box reported the
|
||||
// row's own padding as a misalignment.
|
||||
const projectLeft = await sidebar
|
||||
.getByRole('link', { name: 'Regnskab' })
|
||||
.evaluate((el) => el.querySelector('.dot').getBoundingClientRect().left);
|
||||
expect(
|
||||
Math.abs(shape.left - projectLeft),
|
||||
`gruppen starter ${Math.round(shape.left - projectLeft)}px fra projekterne`
|
||||
).toBeLessThanOrEqual(2);
|
||||
|
||||
// Dragged in, then dragged back out onto "Projekter" — which is the reason
|
||||
// that heading is a drop target at all: with every project filed away there
|
||||
|
||||
@@ -939,14 +939,30 @@
|
||||
/* The chevron on its own, so the name beside it can be a link to the group's
|
||||
page. Sized as a target rather than as a glyph: 11px of arrow is something
|
||||
you miss. */
|
||||
/* The chevron sits on top of the group's dot rather than before it. In the flow
|
||||
it pushed the name 22px further in than a project's, so groups read as if
|
||||
they were filed under the projects above them — the opposite of what they
|
||||
are. Out of the flow, a group's name starts exactly where a project's does.
|
||||
The dot is the resting state; the chevron takes over on hover or focus, and
|
||||
stays put while the group is folded, so a folded group still shows the way
|
||||
back open. */
|
||||
.chevron-button {
|
||||
flex: none;
|
||||
position: absolute;
|
||||
left: calc(var(--s2) - 5px);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--radius-sm);
|
||||
color: inherit;
|
||||
opacity: 0;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.folder-head:hover .chevron-button,
|
||||
.chevron-button:focus-visible,
|
||||
.chevron-button[aria-expanded='false'] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chevron-button:hover {
|
||||
@@ -997,15 +1013,30 @@
|
||||
|
||||
/* Hidden until the heading is hovered, like the section actions on a project
|
||||
page: two buttons permanently beside a label stop it reading as a label. */
|
||||
/* Hidden but still in the flow, these two reserved their own width at all
|
||||
times — enough that ARBEJDE was drawn as "ARBE". They are lifted out of the
|
||||
flow and sit over the right end of the row, so the name has the whole line
|
||||
until somebody actually reaches for them. */
|
||||
.group-action {
|
||||
position: absolute;
|
||||
right: var(--s2);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-faint);
|
||||
padding: 0 var(--s1);
|
||||
flex: none;
|
||||
opacity: 0;
|
||||
background: var(--surface);
|
||||
transition: opacity var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.group-action.remove {
|
||||
right: var(--s2);
|
||||
}
|
||||
|
||||
.group-action:not(.remove) {
|
||||
right: calc(var(--s2) + 3.2em);
|
||||
}
|
||||
|
||||
.folder-head:hover .group-action,
|
||||
.group-action:focus-visible {
|
||||
opacity: 1;
|
||||
@@ -1020,7 +1051,33 @@
|
||||
}
|
||||
|
||||
.folder-head .count {
|
||||
margin-left: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Without min-width a flex item refuses to shrink below its content, so a long
|
||||
group name pushed the count off the row instead of ending in an ellipsis. */
|
||||
.folder-head h2 {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* No left padding of its own: the row already has the same padding as a project
|
||||
row, and the anchor's would be added on top — which is where the last eight
|
||||
pixels of the old indent were hiding. Vertical padding stays, so the heading
|
||||
keeps its height. */
|
||||
.folder-head h2 a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s2);
|
||||
min-width: 0;
|
||||
padding: var(--s2) 0;
|
||||
}
|
||||
|
||||
.folder-head h2 a :global(*:last-child),
|
||||
.folder-head h2 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* The group's own mark, between the chevron and the name — inside the fold
|
||||
|
||||
Reference in New Issue
Block a user