Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
border-bottom: 1px solid var(--line-soft);
flex-shrink: 0;
overflow-x: auto;
// Same fade as the tag rail: a row that scrolls should say so.
mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

.language-rail-label {
Expand Down
9 changes: 6 additions & 3 deletions src/app/notes/header/tag-rail/tag-rail.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
@if (tags().length > 0) {
<div class="tag-rail" role="group" [attr.aria-label]="'notes.tagsGroupLabel' | transloco">
<span class="tag-rail-label">{{ 'notes.tags' | transloco }}</span>
@for (tag of tags(); track tag) {
<app-tag-pill [label]="tag" [active]="activeTags().has(tag)" (toggled)="tagToggled.emit($event)" />
}
<!-- Only the pills scroll: the button below has to stay where it can be clicked. -->
<div class="tag-rail-scroll">
@for (tag of tags(); track tag) {
<app-tag-pill [label]="tag" [active]="activeTags().has(tag)" (toggled)="tagToggled.emit($event)" />
}
</div>
<button type="button" class="tag-rail-manage" data-testid="tag-manage" (click)="manageRequested.emit()">
{{ 'notes.manageTags' | transloco }}
</button>
Expand Down
15 changes: 14 additions & 1 deletion src/app/notes/header/tag-rail/tag-rail.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@
padding: 10px 24px;
border-bottom: 1px solid var(--line-soft);
flex-shrink: 0;
}

// ⚠️ Only the pills scroll. With the Manage button inside the scrolling row, its
// `margin-left: auto` had no free space left to claim once the tags overflowed, and it
// followed them out of the viewport.
.tag-rail-scroll {
display: flex;
align-items: center;
gap: 7px;
flex: 1;
// Or the row pushes the button out again instead of scrolling.
min-width: 0;
overflow-x: auto;
// Falls on empty space while everything fits, so it only shows when there is more.
mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

.tag-rail-label {
Expand All @@ -18,7 +32,6 @@
.tag-rail-manage {
all: unset;
box-sizing: border-box;
margin-left: auto;
flex-shrink: 0;
padding: 3px 9px;
border: 1px solid var(--line-soft);
Expand Down
16 changes: 16 additions & 0 deletions src/app/notes/header/tag-rail/tag-rail.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ describe('TagRailComponent', () => {
expect(rail.getAttribute('aria-label')).toBe('Filtrer par tag');
});

/**
* ⚠️ Structural, because jsdom lays nothing out. Inside the scrolling row the Manage
* button's `margin-left: auto` had no free space to claim once the tags overflowed, so
* it followed them out of the viewport — at forty tags it sat at x=2710 in a rail 1920
* wide. What keeps it reachable is being outside that row.
*/
it('keeps the Manage button out of the row that scrolls', async () => {
fixture.componentRef.setInput('tags', ['alpha', 'beta']);
await fixture.whenStable();

const scroll = fixture.nativeElement.querySelector('.tag-rail-scroll');
expect(scroll.querySelectorAll('app-tag-pill')).toHaveLength(2);
expect(scroll.querySelector('.tag-rail-manage')).toBeNull();
expect(fixture.nativeElement.querySelector('.tag-rail > .tag-rail-manage')).not.toBeNull();
});

it('forwards the toggled event from a tag pill as tagToggled', async () => {
fixture.componentRef.setInput('tags', ['alpha']);
await fixture.whenStable();
Expand Down