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(heading).toBeVisible();
|
||||||
await expect(sidebar.getByText('Træk et projekt herop')).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
|
// Both halves are here because both were once broken at the same time and for
|
||||||
// needs saying out loud: "Omdøb" and "Slet" once got the 20px square meant for
|
// different reasons. "Omdøb" and "Slet" sat in the flow reserving their width
|
||||||
// the "+" icon beside "Projekter", overflowed it, and were drawn overlapping.
|
// even while invisible, so ARBEJDE was drawn as "ARBE"; and the fold chevron
|
||||||
// Both buttons still worked, so every assertion in this file passed.
|
// sat before the name, pushing a group 22px further in than the projects it
|
||||||
const boxes = await sidebar.locator('.folder-head').evaluate((el) =>
|
// contains — so a group read as if it were filed under them. Every assertion
|
||||||
// Links as well as buttons: the group's name is a link to its page now, and
|
// in this file passed through both, because both buttons still worked.
|
||||||
// a measurement that skipped it would miss the widest thing in the row.
|
const shape = await sidebar.locator('.folder-head').evaluate((el) => {
|
||||||
[...el.querySelectorAll('button, a')].map((b) => {
|
const link = el.querySelector('h2 a');
|
||||||
const r = b.getBoundingClientRect();
|
const name = { ...link.querySelector('.dot').getBoundingClientRect().toJSON() };
|
||||||
return { text: b.textContent.trim(), left: r.left, right: r.right };
|
// The last child of the link is the name itself; the dot is a span before it.
|
||||||
})
|
return {
|
||||||
);
|
clipped: link.scrollWidth > link.clientWidth + 1,
|
||||||
for (let i = 1; i < boxes.length; i++) {
|
left: name.left,
|
||||||
expect(
|
right: link.getBoundingClientRect().right,
|
||||||
boxes[i].left,
|
count: el.querySelector('.count')?.getBoundingClientRect().left ?? Infinity
|
||||||
`"${boxes[i - 1].text}" og "${boxes[i].text}" overlapper i gruppehovedet`
|
};
|
||||||
).toBeGreaterThanOrEqual(boxes[i - 1].right);
|
});
|
||||||
}
|
|
||||||
|
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
|
// 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
|
// 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
|
/* 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
|
page. Sized as a target rather than as a glyph: 11px of arrow is something
|
||||||
you miss. */
|
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 {
|
.chevron-button {
|
||||||
flex: none;
|
position: absolute;
|
||||||
|
left: calc(var(--s2) - 5px);
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
color: inherit;
|
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 {
|
.chevron-button:hover {
|
||||||
@@ -997,15 +1013,30 @@
|
|||||||
|
|
||||||
/* Hidden until the heading is hovered, like the section actions on a project
|
/* 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. */
|
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 {
|
.group-action {
|
||||||
|
position: absolute;
|
||||||
|
right: var(--s2);
|
||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
color: var(--ink-faint);
|
color: var(--ink-faint);
|
||||||
padding: 0 var(--s1);
|
padding: 0 var(--s1);
|
||||||
flex: none;
|
flex: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
background: var(--surface);
|
||||||
transition: opacity var(--fast) var(--ease);
|
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,
|
.folder-head:hover .group-action,
|
||||||
.group-action:focus-visible {
|
.group-action:focus-visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -1020,7 +1051,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.folder-head .count {
|
.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
|
/* The group's own mark, between the chevron and the name — inside the fold
|
||||||
|
|||||||
Reference in New Issue
Block a user