Skip to content
Draft
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
13 changes: 13 additions & 0 deletions docs/bell-alignment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- @format -->

# Выравнивание колокольчика в панели профиля

Проверка в браузере на 1440, 1280 и 1024 px использует разметку панели, SVG-иконки и скомпилированные стили компонента. Контейнер уведомлений теперь центрирует SVG независимо от текстовой базовой линии, а красный индикатор расположен относительно него и не влияет на размеры строки. Локальный `viewBox` колокольчика приведён к размеру исходного символа 17 × 19 без изменения внешнего размера 15 × 17, чтобы рисунок не обрезался.

| Ширина | Центр SVG колокольчика | Центр SVG выхода | Центр аватара | Центр блока действий | Скриншот |
| ------- | ---------------------: | ---------------: | ------------: | -------------------: | ---------------------- |
| 1440 px | 34,5 px | 34,5 px | 34,5 px | 34,5 px | [1440](after-1440.png) |
| 1280 px | 34,5 px | 34,5 px | 34,5 px | 34,5 px | [1280](after-1280.png) |
| 1024 px | 34,5 px | 34,5 px | 34,5 px | 34,5 px | [1024](after-1024.png) |

Снимки показывают изолированную панель без пользовательских данных; работу выпадающего блока, clickOutside, выхода и перехода в профиль проверяют компонентные тесты.
Binary file added docs/bell-alignment/after-1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/bell-alignment/after-1280.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/bell-alignment/after-1440.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
<div class="control-panel">
<div class="control-panel__actions">
<div class="control-panel__action" (click)="showNotifications = true">
<div (clickOutside)="onClickOutside()" class="control-panel__bell">
<div
(clickOutside)="onClickOutside()"
class="control-panel__bell control-panel__bell--notifications"
>
@if (hasNotifications() || hasInvites) {
<div class="attention"></div>
}
<i appIcon icon="bell" appWidth="15" appHeight="17" appViewBox="0 0 15 17"></i>
<i appIcon icon="bell" appWidth="15" appHeight="17" appViewBox="0 0 17 19"></i>
@if (invites().length) {
<div class="control-panel__bell--hasInvites"></div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@
&__bell {
position: relative;

&--notifications {
display: flex;
align-items: center;
justify-content: center;
line-height: 0;

> .attention {
position: absolute;
top: 0;
right: 0;
width: 7px;
height: 7px;
background-color: var(--red);
border-radius: 50%;
}
}

&--hasInvites {
position: absolute;
top: -5%;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** @format */

import { provideRouter } from "@angular/router";
import { TestBed } from "@angular/core/testing";
import { User } from "@domain/auth/user.model";
import { ProfileControlPanelComponent } from "./profile-control-panel.component";

describe("ProfileControlPanelComponent: колокольчик", () => {
function render(hasNotifications = false) {
TestBed.configureTestingModule({
providers: [provideRouter([])],
});
const fixture = TestBed.createComponent(ProfileControlPanelComponent);
fixture.componentRef.setInput("user", { id: 42, personal: {} } as User);
fixture.componentRef.setInput("invites", []);
fixture.componentRef.setInput("hasNotifications", hasNotifications);
fixture.detectChanges();
return fixture;
}

it("оставляет индикатор уведомлений внутри локально центрируемого контейнера", () => {
const element = render(true).nativeElement as HTMLElement;
const bell = element.querySelector(".control-panel__bell--notifications");

expect(bell?.querySelector('i[appIcon][icon="bell"]')).not.toBeNull();
expect(bell?.querySelector("svg")?.getAttribute("viewBox")).toBe("0 0 17 19");
expect(bell?.querySelector("svg")?.getAttribute("width")).toBe("15");
expect(bell?.querySelector("svg")?.getAttribute("height")).toBe("17");
expect(bell?.querySelector(".attention")).not.toBeNull();
expect(element.querySelectorAll(".control-panel__bell--notifications")).toHaveLength(1);
});

it("открывает уведомления и закрывает их через clickOutside", async () => {
const fixture = render();
const element = fixture.nativeElement as HTMLElement;

element.querySelector<HTMLElement>(".control-panel__bell--notifications")?.click();
fixture.detectChanges();
expect(element.querySelector(".control-panel__notifications")).not.toBeNull();

document.body.click();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.componentInstance.showNotifications).toBe(false);
expect(element.querySelector(".control-panel__notifications")).toBeNull();
});

it("сохраняет действие выхода и ссылку аватара на профиль", () => {
const fixture = render();
const element = fixture.nativeElement as HTMLElement;
const emitLogout = vi.spyOn(fixture.componentInstance.logout, "emit");
const actions = element.querySelectorAll<HTMLElement>(".control-panel__action");

expect(actions[1].querySelector(".control-panel__bell--notifications")).toBeNull();
actions[1].click();
expect(emitLogout).toHaveBeenCalledOnce();
expect(element.querySelector(".user")?.getAttribute("href")).toBe("/office/profile/42");
});
});