fix(look): mærket og de faste visninger fulgte med op, når listen rullede
Hele sidebjælken rullede, så runen og I dag forsvandt over kanten, så snart der var projekter nok — og de er dem, man er på vej hen til oftest. Alt under de faste visninger ligger i en rulleboks nu, så rullebjælken begynder under dem. En boks indeni frem for `position: sticky` udenom: et klæbende hoved efterlader rullebjælken i fuld højde ved siden af sig, og det var netop den, der skulle starte længere nede. Snippet-erklæringerne bliver liggende udenfor — de tegner ikke noget selv, men de kaldes oppe fra .views, og en snippet erklæret indeni hører kun til indeni. Chevronen for en mappe dækkede gruppens prik med et felt i --surface. På den sænkede bjælke er det en lys firkant, og en foldet gruppe har sin chevron fremme permanent, så klodsen stod der hele tiden. Prikken tones ud i stedet. Det kostede en fælde: en `opacity` under 1 laver en stakkontekst og maler elementet, som var det placeret med `z-index: 0`. Prikken lagde sig dermed oven på knappen, den lige var forsvundet for, og tog klikket — usynlig og stadig i vejen, så gruppen ikke kunne foldes. Ét lag op på knappen, og rækkefølgen er som før. Prøven for vandret rul måler nu begge bokse. Bjælken selv er `overflow: hidden`, så en for bred række ville blive klippet dér og aldrig nå at tælle med — den ville bestå af den forkerte grund. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6c3d66108b
commit
98bfa216d6
@@ -358,6 +358,20 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me
|
|||||||
'aria-expanded',
|
'aria-expanded',
|
||||||
'false'
|
'false'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// En foldet gruppe står med sin chevron fremme permanent, og den lå oven i
|
||||||
|
// prikken med et felt i --surface bag sig — en lys firkant ud for navnet på
|
||||||
|
// den sænkede bjælke, hele tiden. Nu tones prikken ud i stedet, så der er
|
||||||
|
// ingenting at male henover den med.
|
||||||
|
const chevron = await sidebar
|
||||||
|
.locator('.folder-head .chevron-button')
|
||||||
|
.evaluate((el) => ({
|
||||||
|
background: getComputedStyle(el).backgroundColor,
|
||||||
|
dot: getComputedStyle(el.closest('.folder-head').querySelector('.group-dot')).opacity
|
||||||
|
}));
|
||||||
|
expect(chevron.background, 'chevronen har en firkant bag sig').toBe('rgba(0, 0, 0, 0)');
|
||||||
|
expect(chevron.dot, 'prikken ligger stadig under chevronen').toBe('0');
|
||||||
|
|
||||||
await sidebar.getByRole('button', { name: /^Fold Arbejde/ }).click();
|
await sidebar.getByRole('button', { name: /^Fold Arbejde/ }).click();
|
||||||
|
|
||||||
// Colour, which is chosen while renaming rather than from the row. Both are
|
// Colour, which is chosen while renaming rather than from the row. Both are
|
||||||
@@ -1814,6 +1828,14 @@ test('sidebjælken kan ikke scrolles vandret', async ({ page }) => {
|
|||||||
const sideways = await sidebar.evaluate((el) => el.scrollWidth - el.clientWidth);
|
const sideways = await sidebar.evaluate((el) => el.scrollWidth - el.clientWidth);
|
||||||
expect(sideways, 'sidebjælken kan scrolles vandret').toBeLessThanOrEqual(1);
|
expect(sideways, 'sidebjælken kan scrolles vandret').toBeLessThanOrEqual(1);
|
||||||
|
|
||||||
|
// Og rulleboksen indeni, som er den, der faktisk ruller nu. Bjælken selv er
|
||||||
|
// `overflow: hidden`, så en for bred række ville blive klippet dér og aldrig
|
||||||
|
// nå at tælle med ovenfor — målingen ville stå og bestå af den forkerte grund.
|
||||||
|
const inner = await sidebar
|
||||||
|
.locator('.scroller')
|
||||||
|
.evaluate((el) => el.scrollWidth - el.clientWidth);
|
||||||
|
expect(inner, 'rulleboksen kan scrolles vandret').toBeLessThanOrEqual(1);
|
||||||
|
|
||||||
// And the whole page, for the same reason at a different scale.
|
// And the whole page, for the same reason at a different scale.
|
||||||
const page_sideways = await page.evaluate(
|
const page_sideways = await page.evaluate(
|
||||||
() => document.documentElement.scrollWidth - document.documentElement.clientWidth
|
() => document.documentElement.scrollWidth - document.documentElement.clientWidth
|
||||||
@@ -1827,6 +1849,58 @@ test('sidebjælken kan ikke scrolles vandret', async ({ page }) => {
|
|||||||
expect(trouble).toEqual([]);
|
expect(trouble).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mærket og de faste visninger bliver stående, når resten ruller.
|
||||||
|
*
|
||||||
|
* Rullebjælken gik før hele vejen op: runen og I dag forsvandt op over kanten, så
|
||||||
|
* snart der var projekter nok — og de er dem, man er på vej hen til oftest.
|
||||||
|
*
|
||||||
|
* Målt i et lavt vindue, for i et højt er der ikke noget at rulle, og en prøve,
|
||||||
|
* der ikke ruller, består uanset hvad man laver om. Bredden holdes over
|
||||||
|
* telefongrænsen, hvor bjælken bliver til en skuffe og er en anden ting.
|
||||||
|
*/
|
||||||
|
test('sidebjælkens hoved bliver stående, når listen ruller', async ({ page }) => {
|
||||||
|
const trouble = watchForTrouble(page);
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 420 });
|
||||||
|
|
||||||
|
const sidebar = page.getByRole('navigation', { name: 'Hovedmenu' });
|
||||||
|
const scroller = sidebar.locator('.scroller');
|
||||||
|
await expect(scroller).toBeVisible();
|
||||||
|
|
||||||
|
// Rullebjælken begynder under de faste visninger og ikke bag dem.
|
||||||
|
const geometry = await sidebar.evaluate((el) => {
|
||||||
|
const box = (sel) => el.querySelector(sel).getBoundingClientRect().toJSON();
|
||||||
|
const scroll = el.querySelector('.scroller');
|
||||||
|
return {
|
||||||
|
views: box('.views'),
|
||||||
|
scroller: box('.scroller'),
|
||||||
|
overflows: scroll.scrollHeight - scroll.clientHeight
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(geometry.overflows, 'der er ikke noget at rulle — prøven beviser intet').toBeGreaterThan(
|
||||||
|
0
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
geometry.scroller.top,
|
||||||
|
'rulleboksen begynder oppe i de faste visninger'
|
||||||
|
).toBeGreaterThanOrEqual(geometry.views.bottom - 1);
|
||||||
|
|
||||||
|
// Og hovedet bliver, hvor det er, hele vejen ned.
|
||||||
|
const brand = sidebar.locator('.brand');
|
||||||
|
const before = await brand.boundingBox();
|
||||||
|
await scroller.evaluate((el) => el.scrollTo(0, el.scrollHeight));
|
||||||
|
await expect
|
||||||
|
.poll(async () => scroller.evaluate((el) => el.scrollTop))
|
||||||
|
.toBeGreaterThan(0);
|
||||||
|
const after = await brand.boundingBox();
|
||||||
|
expect(Math.abs(after.y - before.y), 'mærket fulgte med op').toBeLessThanOrEqual(1);
|
||||||
|
await expect(sidebar.getByRole('link', { name: 'I dag' })).toBeInViewport();
|
||||||
|
|
||||||
|
expect(trouble).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A project can have more than one section.
|
* A project can have more than one section.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -639,6 +639,17 @@
|
|||||||
</h2>
|
</h2>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
<!-- Alt herunder ruller; mærket og de faste visninger gør ikke.
|
||||||
|
Hele sidebjælken rullede før, så runen og I dag forsvandt op over kanten,
|
||||||
|
så snart der var projekter nok — og de er dem, man er på vej hen til
|
||||||
|
oftest. En rulleboks indeni frem for `position: sticky` udenom, fordi et
|
||||||
|
klæbende hoved efterlader rullebjælken i fuld højde ved siden af sig: den
|
||||||
|
skal begynde under hovedet, ikke bag det.
|
||||||
|
|
||||||
|
Snippet-erklæringerne bliver liggende udenfor. De tegner ikke noget selv,
|
||||||
|
men de kaldes oppe fra .views, og en snippet erklæret herinde hører kun
|
||||||
|
til herinde. -->
|
||||||
|
<div class="scroller">
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<!-- The heading is also the drop target for "no group": without it, a project
|
<!-- The heading is also the drop target for "no group": without it, a project
|
||||||
dragged into a group could only be got out again by emptying the group,
|
dragged into a group could only be got out again by emptying the group,
|
||||||
@@ -935,6 +946,7 @@
|
|||||||
<span class="signout">{t('nav.signOut')}</span>
|
<span class="signout">{t('nav.signOut')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Also a slider for the keyboard: arrow keys move it, and Home puts it back.
|
<!-- Also a slider for the keyboard: arrow keys move it, and Home puts it back.
|
||||||
A drag handle with no keyboard equivalent is a setting some people simply
|
A drag handle with no keyboard equivalent is a setting some people simply
|
||||||
cannot reach. -->
|
cannot reach. -->
|
||||||
@@ -970,11 +982,31 @@
|
|||||||
padding: var(--s4) var(--s3);
|
padding: var(--s4) var(--s3);
|
||||||
background: var(--surface-sunken);
|
background: var(--surface-sunken);
|
||||||
border-right: 1px solid var(--line);
|
border-right: 1px solid var(--line);
|
||||||
overflow-y: auto;
|
/* Selve bjælken ruller ikke længere — .scroller gør. `hidden` frem for
|
||||||
|
ingenting, fordi den stadig er den yderste kant: løber en række over,
|
||||||
|
skal den klippes her og ikke skubbe kolonnen bredere. */
|
||||||
|
overflow: hidden;
|
||||||
padding-left: max(var(--s3), env(safe-area-inset-left));
|
padding-left: max(var(--s3), env(safe-area-inset-left));
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* `min-height: 0`, ellers nægter et flex-element at blive lavere end sit
|
||||||
|
indhold, og boksen ville vokse ned gennem bunden af skærmen i stedet for at
|
||||||
|
rulle — samme fælde som `min-width: 0` på kontonavnet længere nede.
|
||||||
|
|
||||||
|
Ikke `overflow-x: hidden` her: `overflow-y: auto` gør den anden akse til
|
||||||
|
`auto` af sig selv, og en vandret rullebjælke under menuen er en fejl i en
|
||||||
|
række, ikke i boksen. At klippe den ville skjule fejlen; prøven måler begge
|
||||||
|
bokse. */
|
||||||
|
.scroller {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--s5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Wider than it looks: a 1px target is a target you miss. The visible line stays
|
/* Wider than it looks: a 1px target is a target you miss. The visible line stays
|
||||||
the border; this only widens what the pointer hits.
|
the border; this only widens what the pointer hits.
|
||||||
|
|
||||||
@@ -1213,6 +1245,11 @@
|
|||||||
The dot is the resting state; the chevron takes over on hover or focus, and
|
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
|
stays put while the group is folded, so a folded group still shows the way
|
||||||
back open. */
|
back open. */
|
||||||
|
/* Uden baggrund. Knappen dækkede prikken med et felt i --surface, og på den
|
||||||
|
sænkede bjælke er det en lys firkant: en foldet gruppe stod med en hvid
|
||||||
|
klods ud for navnet permanent, fordi chevronen bliver stående, når gruppen
|
||||||
|
er lukket. Prikken tones ud i stedet for at blive dækket — det er samme
|
||||||
|
skifte, uden noget at male henover den med. */
|
||||||
.chevron-button {
|
.chevron-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(var(--s2) - 5px);
|
left: calc(var(--s2) - 5px);
|
||||||
@@ -1223,7 +1260,13 @@
|
|||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background: var(--surface);
|
/* Fordi prikken herunder tones ud frem for at blive dækket. En `opacity`
|
||||||
|
under 1 laver en stakkontekst og maler elementet, som var det placeret
|
||||||
|
med `z-index: 0` — så prikken, der står senere i DOM'en, lagde sig oven
|
||||||
|
på knappen i det øjeblik den forsvandt. Den var usynlig og tog stadig
|
||||||
|
klikket: prøven brugte tredive sekunder på at folde en gruppe, der ikke
|
||||||
|
ville foldes. Ét lag op, og rækkefølgen er den samme som før. */
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-head:hover .chevron-button,
|
.folder-head:hover .chevron-button,
|
||||||
@@ -1232,9 +1275,18 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Præcis de tre tilfælde ovenfor, læst nedad i stedet for opad: står chevronen
|
||||||
|
der, er prikken væk. `opacity` og ikke `display`, for de to ligger oven i
|
||||||
|
hinanden — forsvandt prikken ud af flowet, ville navnet rykke sig hver gang
|
||||||
|
pilen kom frem. */
|
||||||
|
.folder-head:hover .group-dot,
|
||||||
|
.folder-head:has(.chevron-button:focus-visible) .group-dot,
|
||||||
|
.folder-head:has(.chevron-button[aria-expanded='false']) .group-dot {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chevron-button:hover {
|
.chevron-button:hover {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
background: var(--surface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fold a,
|
.fold a,
|
||||||
|
|||||||
@@ -437,7 +437,6 @@
|
|||||||
padding-left: max(var(--s3), env(safe-area-inset-left));
|
padding-left: max(var(--s3), env(safe-area-inset-left));
|
||||||
padding-right: var(--s3);
|
padding-right: var(--s3);
|
||||||
border-right: 1px solid var(--line);
|
border-right: 1px solid var(--line);
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user