Fix notification dropdown accessibility

This commit is contained in:
Chocobozzz 2024-09-20 10:45:16 +02:00
parent 8ce037b0c0
commit fff5a323fd
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 108 additions and 109 deletions

View File

@ -79,7 +79,7 @@
</div> </div>
@defer (when isLoggedIn) { @defer (when isLoggedIn) {
<my-notification (navigate)="onActiveLinkScrollToAnchor($event)"></my-notification> <my-notification-dropdown (navigate)="onActiveLinkScrollToAnchor($event)"></my-notification-dropdown>
} }
</div> </div>

View File

@ -116,7 +116,7 @@ nav {
} }
} }
my-notification { my-notification-dropdown {
@include margin-left(auto); @include margin-left(auto);
@include margin-right(15px); @include margin-right(15px);
} }

View File

@ -27,7 +27,7 @@ import debug from 'debug'
import { forkJoin, Subscription } from 'rxjs' import { forkJoin, Subscription } from 'rxjs'
import { first, switchMap } from 'rxjs/operators' import { first, switchMap } from 'rxjs/operators'
import { LanguageChooserComponent } from './language-chooser.component' import { LanguageChooserComponent } from './language-chooser.component'
import { NotificationComponent } from './notification.component' import { NotificationDropdownComponent } from './notification-dropdown.component'
import { QuickSettingsModalComponent } from './quick-settings-modal.component' import { QuickSettingsModalComponent } from './quick-settings-modal.component'
const debugLogger = debug('peertube:menu:MenuComponent') const debugLogger = debug('peertube:menu:MenuComponent')
@ -39,7 +39,7 @@ const debugLogger = debug('peertube:menu:MenuComponent')
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule,
NotificationComponent, NotificationDropdownComponent,
ActorAvatarComponent, ActorAvatarComponent,
InputSwitchComponent, InputSwitchComponent,
SignupLabelComponent, SignupLabelComponent,

View File

@ -0,0 +1,70 @@
<ng-template #notificationNumber>
<div *ngIf="unreadNotifications > 0 && unreadNotifications < 100" class="unread-notifications">{{ unreadNotifications }}</div>
<div *ngIf="unreadNotifications >= 100" class="unread-notifications">99+</div>
</ng-template>
@if (isInMobileView) {
<div i18n-title title="View your notifications" class="notification-inbox-link">
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
<a routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)">
<my-global-icon iconName="bell"></my-global-icon>
</a>
</div>
} @else {
<div
ngbDropdown autoClose="outside" placement="bottom" container="body" dropdownClass="dropdown-notifications"
#dropdown="ngbDropdown" (shown)="onDropdownShown()" (hidden)="onDropdownHidden()"
>
<button
i18n-title title="View your notifications"
class="border-0 text-start disable-dropdown-caret" [ngClass]="{ 'notification-inbox-dropdown': true, 'shown': opened, 'hidden': isInMobileView }"
ngbDropdownToggle
>
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
<my-global-icon iconName="bell"></my-global-icon>
</button>
<div ngbDropdownMenu>
<div class="content" [ngClass]="{ loaded: loaded }">
<div class="notifications-header">
<div i18n>Notifications</div>
<div>
<button
*ngIf="unreadNotifications"
i18n-title title="Mark all as read" class="me-2"
(click)="markAllAsRead()"
>
<my-global-icon iconName="tick"></my-global-icon>
</button>
<a
i18n-title title="Update your notification preferences"
routerLink="/my-account/settings" fragment="notifications"
#settingsNotifications (click)="onNavigate(settingsNotifications)"
>
<my-global-icon iconName="cog"></my-global-icon>
</a>
</div>
</div>
<div *ngIf="!loaded" class="loader mt-4">
<my-loader size="xl" [loading]="!loaded"></my-loader>
</div>
<my-user-notifications
[ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10"
[markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()"
></my-user-notifications>
<a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
<my-global-icon class="me-1" iconName="bell" aria-hidden="true"></my-global-icon>
<span i18n>See all your notifications</span>
</a>
</div>
</div>
</div>
}

View File

@ -5,7 +5,7 @@
scrollbar-color: auto; scrollbar-color: auto;
} }
.notification-inbox-popover { .notification-inbox-dropdown {
padding: 10px; padding: 10px;
} }
@ -13,7 +13,7 @@
padding: 13px 10px; padding: 13px 10px;
} }
.notification-inbox-popover, .notification-inbox-dropdown,
.notification-inbox-link a { .notification-inbox-link a {
transition: all .1s ease-in-out; transition: all .1s ease-in-out;
border-radius: 25px; border-radius: 25px;
@ -33,7 +33,7 @@
} }
} }
.notification-inbox-popover.shown, .notification-inbox-dropdown.shown,
.notification-inbox-link a.active { .notification-inbox-link a.active {
background-color: rgba(255, 255, 255, 0.28); background-color: rgba(255, 255, 255, 0.28);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325); box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
@ -41,21 +41,17 @@
@include apply-svg-color(#fff); @include apply-svg-color(#fff);
} }
.notification-inbox-popover.hidden { .notification-inbox-dropdown.hidden {
display: none; display: none;
} }
::ng-deep { ::ng-deep {
.popover-notifications.popover { .dropdown-notifications {
max-width: none; max-width: none;
top: -6px !important; top: -6px !important;
left: 7px !important; left: 7px !important;
.arrow { .dropdown-menu {
display: none;
}
.popover-body {
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
font-family: $main-fonts; font-family: $main-fonts;
@ -130,20 +126,23 @@
} }
.all-notifications { .all-notifications {
display: flex; border-top: 1px solid rgba(0, 0, 0, 0.1);
align-items: center; margin-top: -1px; // To not have 2 borders with the last notification
justify-content: center; text-align: center;
font-weight: $font-semibold; font-weight: $font-semibold;
color: pvar(--mainForegroundColor); color: pvar(--mainForegroundColor);
padding: 7px 0; padding: 0.75rem 0;
margin-top: auto; text-decoration: underline !important;
text-decoration: none;
&:hover {
opacity: 0.85;
}
} }
} }
} }
} }
.notification-inbox-popover, .notification-inbox-dropdown,
.notification-inbox-link { .notification-inbox-link {
cursor: pointer; cursor: pointer;
position: relative; position: relative;

View File

@ -6,14 +6,14 @@ import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.compon
import { LoaderComponent } from '@app/shared/shared-main/loaders/loader.component' import { LoaderComponent } from '@app/shared/shared-main/loaders/loader.component'
import { UserNotificationService } from '@app/shared/shared-main/users/user-notification.service' import { UserNotificationService } from '@app/shared/shared-main/users/user-notification.service'
import { UserNotificationsComponent } from '@app/shared/standalone-notifications/user-notifications.component' import { UserNotificationsComponent } from '@app/shared/standalone-notifications/user-notifications.component'
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbDropdownModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import { Subject, Subscription } from 'rxjs' import { Subject, Subscription } from 'rxjs'
import { filter } from 'rxjs/operators' import { filter } from 'rxjs/operators'
@Component({ @Component({
selector: 'my-notification', selector: 'my-notification-dropdown',
templateUrl: './notification.component.html', templateUrl: './notification-dropdown.component.html',
styleUrls: [ './notification.component.scss' ], styleUrls: [ './notification-dropdown.component.scss' ],
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule,
@ -22,11 +22,12 @@ import { filter } from 'rxjs/operators'
GlobalIconComponent, GlobalIconComponent,
LoaderComponent, LoaderComponent,
RouterLink, RouterLink,
RouterLinkActive RouterLinkActive,
NgbDropdownModule
] ]
}) })
export class NotificationComponent implements OnInit, OnDestroy { export class NotificationDropdownComponent implements OnInit, OnDestroy {
@ViewChild('popover', { static: true }) popover: NgbPopover @ViewChild('dropdown', { static: false }) dropdown: NgbDropdown
@Output() navigate = new EventEmitter<HTMLAnchorElement>() @Output() navigate = new EventEmitter<HTMLAnchorElement>()
@ -61,7 +62,7 @@ export class NotificationComponent implements OnInit, OnDestroy {
this.routeSub = this.router.events this.routeSub = this.router.events
.pipe(filter(event => event instanceof NavigationEnd)) .pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => this.closePopover()) .subscribe(() => this.closeDropdown())
} }
ngOnDestroy () { ngOnDestroy () {
@ -73,29 +74,17 @@ export class NotificationComponent implements OnInit, OnDestroy {
return this.screenService.isInMobileView() return this.screenService.isInMobileView()
} }
closePopover () { closeDropdown () {
this.popover.close() if (this.dropdown) this.dropdown.close()
} }
onPopoverShown () { onDropdownShown () {
this.opened = true this.opened = true
document.querySelector('nav').scrollTo(0, 0) // Reset menu scroll to easy lock
// eslint-disable-next-line @typescript-eslint/unbound-method
document.querySelector('nav').addEventListener('scroll', this.onMenuScrollEvent)
} }
onPopoverHidden () { onDropdownHidden () {
this.loaded = false this.loaded = false
this.opened = false this.opened = false
// eslint-disable-next-line @typescript-eslint/unbound-method
document.querySelector('nav').removeEventListener('scroll', this.onMenuScrollEvent)
}
// Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
onMenuScrollEvent () {
document.querySelector('nav').scrollTo(0, 0)
} }
onNotificationLoaded () { onNotificationLoaded () {
@ -103,7 +92,7 @@ export class NotificationComponent implements OnInit, OnDestroy {
} }
onNavigate (link: HTMLAnchorElement) { onNavigate (link: HTMLAnchorElement) {
this.closePopover() this.closeDropdown()
this.navigate.emit(link) this.navigate.emit(link)
} }

View File

@ -1,63 +0,0 @@
<ng-template #notificationNumber>
<div *ngIf="unreadNotifications > 0 && unreadNotifications < 100" class="unread-notifications">{{ unreadNotifications }}</div>
<div *ngIf="unreadNotifications >= 100" class="unread-notifications">99+</div>
</ng-template>
<button
[ngbPopover]="popContent" autoClose="outside" placement="bottom" container="body" popoverClass="popover-notifications"
i18n-title title="View your notifications"
class="border-0 text-start" [ngClass]="{ 'notification-inbox-popover': true, 'shown': opened, 'hidden': isInMobileView }"
#popover="ngbPopover" (shown)="onPopoverShown()" (hidden)="onPopoverHidden()"
>
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
<my-global-icon iconName="bell"></my-global-icon>
</button>
<div *ngIf="isInMobileView" i18n-title title="View your notifications" class="notification-inbox-link">
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
<a routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)">
<my-global-icon iconName="bell"></my-global-icon>
</a>
</div>
<ng-template #popContent>
<div class="content" [ngClass]="{ loaded: loaded }">
<div class="notifications-header">
<div i18n>Notifications</div>
<div>
<button
*ngIf="unreadNotifications"
i18n-title title="Mark all as read" class="me-2"
(click)="markAllAsRead()"
>
<my-global-icon iconName="tick"></my-global-icon>
</button>
<a
i18n-title title="Update your notification preferences"
routerLink="/my-account/settings" fragment="notifications"
#settingsNotifications (click)="onNavigate(settingsNotifications)"
>
<my-global-icon iconName="cog"></my-global-icon>
</a>
</div>
</div>
<div *ngIf="!loaded" class="loader mt-4">
<my-loader size="xl" [loading]="!loaded"></my-loader>
</div>
<my-user-notifications
[ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10"
[markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()"
></my-user-notifications>
<a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
<my-global-icon class="me-1" iconName="bell" aria-hidden="true"></my-global-icon>
<span i18n>See all your notifications</span>
</a>
</div>
</ng-template>

View File

@ -102,6 +102,10 @@ body {
margin: 0.3rem 0; margin: 0.3rem 0;
} }
.disable-dropdown-caret::after {
display: none;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Alert // Alert
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------