diff --git a/docs/bell-alignment/README.md b/docs/bell-alignment/README.md new file mode 100644 index 000000000..28e3ddf3a --- /dev/null +++ b/docs/bell-alignment/README.md @@ -0,0 +1,13 @@ + + +# Выравнивание колокольчика в панели профиля + +Проверка в браузере на 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, выхода и перехода в профиль проверяют компонентные тесты. diff --git a/docs/bell-alignment/after-1024.png b/docs/bell-alignment/after-1024.png new file mode 100644 index 000000000..1d8f29af1 Binary files /dev/null and b/docs/bell-alignment/after-1024.png differ diff --git a/docs/bell-alignment/after-1280.png b/docs/bell-alignment/after-1280.png new file mode 100644 index 000000000..f5bc33996 Binary files /dev/null and b/docs/bell-alignment/after-1280.png differ diff --git a/docs/bell-alignment/after-1440.png b/docs/bell-alignment/after-1440.png new file mode 100644 index 000000000..ed04b86ee Binary files /dev/null and b/docs/bell-alignment/after-1440.png differ diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html index 7727bd3eb..23e34392a 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html @@ -3,11 +3,14 @@
-
+
@if (hasNotifications() || hasInvites) {
} - + @if (invites().length) {
} diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss index d0652ceab..3605c3121 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss @@ -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%; diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.spec.ts b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.spec.ts new file mode 100644 index 000000000..c8dc6a7b1 --- /dev/null +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.spec.ts @@ -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(".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(".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"); + }); +});