Skip to content

Commit 2e62028

Browse files
committed
added clear all button for all stale unlock indicator
1 parent f96a085 commit 2e62028

6 files changed

Lines changed: 383 additions & 1 deletion

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Directive, ElementRef, HostListener, Input, OnDestroy, Renderer2 } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[appTooltip]'
5+
})
6+
export class TooltipDirective implements OnDestroy {
7+
@Input('appTooltip') tooltipText: string;
8+
@Input() position: 'top' | 'bottom' | 'left' | 'right' = 'bottom';
9+
@Input() tooltipClass = '';
10+
11+
private tooltip: HTMLElement | null = null;
12+
private arrow: HTMLElement | null = null;
13+
private hasBeenShown = false;
14+
15+
constructor(private el: ElementRef, private renderer: Renderer2) {}
16+
17+
@HostListener('mouseenter') onMouseEnter(): void {
18+
this.show();
19+
}
20+
21+
@HostListener('mouseleave') onMouseLeave(): void {
22+
this.hide();
23+
}
24+
25+
@HostListener('focus') onFocus(): void {
26+
this.show();
27+
}
28+
29+
@HostListener('blur') onBlur(): void {
30+
this.hide();
31+
}
32+
33+
private show(): void {
34+
if (this.tooltip || !this.tooltipText) {
35+
return;
36+
}
37+
38+
this.tooltip = this.renderer.createElement('div');
39+
this.renderer.addClass(this.tooltip, 'app-tooltip');
40+
if (this.tooltipClass) {
41+
this.tooltipClass.split(' ').forEach(cls => {
42+
if (cls) {
43+
this.renderer.addClass(this.tooltip, cls);
44+
}
45+
});
46+
}
47+
this.renderer.setProperty(this.tooltip, 'innerHTML', this.tooltipText);
48+
49+
this.arrow = this.renderer.createElement('div');
50+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow');
51+
52+
// append to body (not to component)
53+
this.renderer.appendChild(this.tooltip, this.arrow);
54+
this.renderer.appendChild(document.body, this.tooltip);
55+
56+
// position after a slight delay to ensure proper rendering
57+
setTimeout(() => {
58+
this.setPosition();
59+
this.renderer.addClass(this.tooltip, 'app-tooltip-visible');
60+
this.hasBeenShown = true;
61+
}, 20);
62+
}
63+
64+
private hide(): void {
65+
if (!this.tooltip) {
66+
return;
67+
}
68+
69+
this.renderer.removeClass(this.tooltip, 'app-tooltip-visible');
70+
71+
// remove after transition completes
72+
setTimeout(() => {
73+
if (this.tooltip && this.tooltip.parentNode) {
74+
this.renderer.removeChild(document.body, this.tooltip);
75+
this.tooltip = null;
76+
this.arrow = null;
77+
}
78+
}, 300);
79+
}
80+
81+
private setPosition(): void {
82+
if (!this.tooltip) {
83+
return;
84+
}
85+
86+
const hostRect = this.el.nativeElement.getBoundingClientRect();
87+
const tooltipRect = this.tooltip.getBoundingClientRect();
88+
89+
let top = 0;
90+
let left = 0;
91+
92+
switch (this.position) {
93+
case 'top':
94+
top = hostRect.top - tooltipRect.height - 10;
95+
left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);
96+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-bottom');
97+
break;
98+
case 'bottom':
99+
top = hostRect.bottom + 10;
100+
left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);
101+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-top');
102+
break;
103+
case 'left':
104+
top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);
105+
left = hostRect.left - tooltipRect.width - 10;
106+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-right');
107+
break;
108+
case 'right':
109+
top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);
110+
left = hostRect.right + 10;
111+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-left');
112+
break;
113+
}
114+
115+
// ensure tooltip is within viewport
116+
if (top < 0) {
117+
top = hostRect.bottom + 10;
118+
this.renderer.removeClass(this.arrow, 'app-tooltip-arrow-bottom');
119+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-top');
120+
}
121+
122+
if (left < 0) {
123+
left = 10;
124+
}
125+
126+
if (left + tooltipRect.width > window.innerWidth) {
127+
left = window.innerWidth - tooltipRect.width - 10;
128+
}
129+
130+
// set arrow position based on host element
131+
const arrowLeft = hostRect.left - left + (hostRect.width / 2) - 6;
132+
this.renderer.setStyle(this.arrow, 'left', `${arrowLeft}px`);
133+
134+
// set tooltip position dynamically
135+
this.renderer.setStyle(this.tooltip, 'top', `${top}px`);
136+
this.renderer.setStyle(this.tooltip, 'left', `${left}px`);
137+
}
138+
139+
ngOnDestroy(): void {
140+
if (this.tooltip && this.tooltip.parentNode) {
141+
this.renderer.removeChild(document.body, this.tooltip);
142+
}
143+
}
144+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { TooltipDirective } from './tooltip.directive';
4+
5+
@NgModule({
6+
declarations: [TooltipDirective],
7+
imports: [CommonModule],
8+
exports: [TooltipDirective]
9+
})
10+
export class TooltipModule {
11+
constructor() {
12+
// inject tooltip styles into document head
13+
if (!document.querySelector('style[data-tooltip-styles]')) {
14+
const styleElement = document.createElement('style');
15+
styleElement.setAttribute('data-tooltip-styles', 'true');
16+
styleElement.textContent = `
17+
.app-tooltip {
18+
position: fixed;
19+
background-color: rgba(0, 0, 0, 0.9);
20+
color: #fff;
21+
padding: 8px 12px;
22+
border-radius: 4px;
23+
font-size: 12px;
24+
max-width: 300px;
25+
white-space: normal;
26+
word-wrap: break-word;
27+
pointer-events: none;
28+
opacity: 0;
29+
visibility: hidden;
30+
transition: opacity 0.3s ease, visibility 0.3s ease;
31+
z-index: 100000;
32+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25);
33+
}
34+
35+
.app-tooltip.app-tooltip-visible {
36+
opacity: 1;
37+
visibility: visible;
38+
}
39+
40+
.app-tooltip.app-tooltip-warning {
41+
background-color: gray;
42+
}
43+
44+
.app-tooltip-arrow {
45+
position: absolute;
46+
width: 0;
47+
height: 0;
48+
}
49+
50+
.app-tooltip-arrow.app-tooltip-arrow-top {
51+
top: -6px;
52+
border-left: 6px solid transparent;
53+
border-right: 6px solid transparent;
54+
border-bottom: 6px solid rgba(0, 0, 0, 0.9);
55+
}
56+
57+
.app-tooltip-arrow.app-tooltip-arrow-bottom {
58+
bottom: -6px;
59+
border-left: 6px solid transparent;
60+
border-right: 6px solid transparent;
61+
border-top: 6px solid rgba(0, 0, 0, 0.9);
62+
}
63+
64+
.app-tooltip-arrow.app-tooltip-arrow-left {
65+
left: -6px;
66+
border-top: 6px solid transparent;
67+
border-bottom: 6px solid transparent;
68+
border-right: 6px solid rgba(0, 0, 0, 0.9);
69+
}
70+
71+
.app-tooltip-arrow.app-tooltip-arrow-right {
72+
right: -6px;
73+
border-top: 6px solid transparent;
74+
border-bottom: 6px solid transparent;
75+
border-left: 6px solid rgba(0, 0, 0, 0.9);
76+
}
77+
`;
78+
document.head.appendChild(styleElement);
79+
}
80+
}
81+
}

projects/v3/src/app/pages/notifications/notifications.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { NotificationsPageRoutingModule } from './notifications-routing.module';
88

99
import { NotificationsPage } from './notifications.page';
1010
import { ComponentsModule } from '@v3/app/components/components.module';
11+
import { TooltipModule } from '@v3/app/directives/tooltip/tooltip.module';
1112

1213
@NgModule({
1314
imports: [
@@ -16,6 +17,7 @@ import { ComponentsModule } from '@v3/app/components/components.module';
1617
IonicModule,
1718
NotificationsPageRoutingModule,
1819
ComponentsModule,
20+
TooltipModule,
1921
],
2022
declarations: [
2123
NotificationsPage,

projects/v3/src/app/pages/notifications/notifications.page.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ <h1 class="for-accessibility" i18n>Notifications</h1>
1212
</ion-buttons>
1313

1414
<ion-title class="headline-4 grey-75" i18n>Notifications</ion-title>
15+
16+
<ion-buttons slot="end">
17+
<ion-button
18+
*ngIf="hasUnlockIndicators"
19+
(click)="markAllUnlockIndicatorsAsRead()"
20+
(keydown)="markAllUnlockIndicatorsAsRead($event)"
21+
fill="clear"
22+
size="small"
23+
class="mark-all-button"
24+
[disabled]="markingInProgress"
25+
[appTooltip]="'Warning: This action cannot be undone. All unlock indicators will be permanently cleared.'"
26+
position="bottom"
27+
tooltipClass="app-tooltip-warning"
28+
i18n-appTooltip>
29+
<ion-icon name="checkmark-done-outline" slot="start"></ion-icon>
30+
<span i18n>Clear All Indicators</span>
31+
</ion-button>
32+
</ion-buttons>
1533
</ion-toolbar>
1634
</ion-header>
1735

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
:host {
22
--ion-padding: 8px;
33
}
4+
5+
.mark-all-button {
6+
--color: var(--ion-color-primary);
7+
8+
ion-icon {
9+
margin-right: 4px;
10+
}
11+
}
12+
13+
.mark-all-button:disabled {
14+
--color: var(--ion-color-medium);
15+
pointer-events: none;
16+
}

0 commit comments

Comments
 (0)