Skip to content

Commit 470acc9

Browse files
committed
added clear all button for all stale unlock indicator
1 parent f96a085 commit 470acc9

6 files changed

Lines changed: 385 additions & 1 deletion

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
// create tooltip element
39+
this.tooltip = this.renderer.createElement('div');
40+
this.renderer.addClass(this.tooltip, 'app-tooltip');
41+
if (this.tooltipClass) {
42+
this.tooltipClass.split(' ').forEach(cls => {
43+
if (cls) {
44+
this.renderer.addClass(this.tooltip, cls);
45+
}
46+
});
47+
}
48+
this.renderer.setProperty(this.tooltip, 'innerHTML', this.tooltipText);
49+
50+
// create arrow element
51+
this.arrow = this.renderer.createElement('div');
52+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow');
53+
54+
// append to body (not to component)
55+
this.renderer.appendChild(this.tooltip, this.arrow);
56+
this.renderer.appendChild(document.body, this.tooltip);
57+
58+
// position after a slight delay to ensure proper rendering
59+
setTimeout(() => {
60+
this.setPosition();
61+
this.renderer.addClass(this.tooltip, 'app-tooltip-visible');
62+
this.hasBeenShown = true;
63+
}, 20);
64+
}
65+
66+
private hide(): void {
67+
if (!this.tooltip) {
68+
return;
69+
}
70+
71+
this.renderer.removeClass(this.tooltip, 'app-tooltip-visible');
72+
73+
// remove after transition completes
74+
setTimeout(() => {
75+
if (this.tooltip && this.tooltip.parentNode) {
76+
this.renderer.removeChild(document.body, this.tooltip);
77+
this.tooltip = null;
78+
this.arrow = null;
79+
}
80+
}, 300);
81+
}
82+
83+
private setPosition(): void {
84+
if (!this.tooltip) {
85+
return;
86+
}
87+
88+
const hostRect = this.el.nativeElement.getBoundingClientRect();
89+
const tooltipRect = this.tooltip.getBoundingClientRect();
90+
91+
let top = 0;
92+
let left = 0;
93+
94+
switch (this.position) {
95+
case 'top':
96+
top = hostRect.top - tooltipRect.height - 10;
97+
left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);
98+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-bottom');
99+
break;
100+
case 'bottom':
101+
top = hostRect.bottom + 10;
102+
left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);
103+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-top');
104+
break;
105+
case 'left':
106+
top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);
107+
left = hostRect.left - tooltipRect.width - 10;
108+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-right');
109+
break;
110+
case 'right':
111+
top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);
112+
left = hostRect.right + 10;
113+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-left');
114+
break;
115+
}
116+
117+
// ensure tooltip is within viewport
118+
if (top < 0) {
119+
top = hostRect.bottom + 10;
120+
this.renderer.removeClass(this.arrow, 'app-tooltip-arrow-bottom');
121+
this.renderer.addClass(this.arrow, 'app-tooltip-arrow-top');
122+
}
123+
124+
if (left < 0) {
125+
left = 10;
126+
}
127+
128+
if (left + tooltipRect.width > window.innerWidth) {
129+
left = window.innerWidth - tooltipRect.width - 10;
130+
}
131+
132+
// set arrow position based on host element
133+
const arrowLeft = hostRect.left - left + (hostRect.width / 2) - 6;
134+
this.renderer.setStyle(this.arrow, 'left', `${arrowLeft}px`);
135+
136+
// set tooltip position dynamically
137+
this.renderer.setStyle(this.tooltip, 'top', `${top}px`);
138+
this.renderer.setStyle(this.tooltip, 'left', `${left}px`);
139+
}
140+
141+
ngOnDestroy(): void {
142+
if (this.tooltip && this.tooltip.parentNode) {
143+
this.renderer.removeChild(document.body, this.tooltip);
144+
}
145+
}
146+
}
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)