feat(ui): add reusable information tooltip
This commit is contained in:
11
src/app/shared/components/tooltip/tooltip.component.html
Normal file
11
src/app/shared/components/tooltip/tooltip.component.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="tooltip-trigger"
|
||||||
|
[attr.aria-describedby]="tooltipId"
|
||||||
|
[attr.aria-label]="message()"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-circle-info" aria-hidden="true"></i>
|
||||||
|
<span class="tooltip-message" [id]="tooltipId" role="tooltip">
|
||||||
|
{{ message() }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
62
src/app/shared/components/tooltip/tooltip.component.scss
Normal file
62
src/app/shared/components/tooltip/tooltip.component.scss
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
:host {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trigger {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--tenant-primary, var(--color-primary, #0d6efd));
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
border-radius: 50%;
|
||||||
|
outline: 2px solid currentColor;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .tooltip-message,
|
||||||
|
&:focus-visible .tooltip-message {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -0.25rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-message {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1100;
|
||||||
|
bottom: calc(100% + 0.625rem);
|
||||||
|
left: 50%;
|
||||||
|
width: max-content;
|
||||||
|
max-width: min(16rem, 75vw);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--tenant-primary, var(--color-primary, #0d6efd));
|
||||||
|
background: #fff;
|
||||||
|
color: #212529;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.35;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
white-space: normal;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
transition:
|
||||||
|
opacity 0.15s ease,
|
||||||
|
transform 0.15s ease,
|
||||||
|
visibility 0.15s ease;
|
||||||
|
}
|
||||||
32
src/app/shared/components/tooltip/tooltip.component.spec.ts
Normal file
32
src/app/shared/components/tooltip/tooltip.component.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||||
|
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
||||||
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { TooltipComponent } from './tooltip.component';
|
||||||
|
|
||||||
|
describe('TooltipComponent', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
try {
|
||||||
|
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
||||||
|
} catch {
|
||||||
|
// Test environment may already be initialized by another setup entrypoint.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => TestBed.resetTestingModule());
|
||||||
|
|
||||||
|
it('renders the reusable danger information tooltip', async () => {
|
||||||
|
await TestBed.configureTestingModule({ imports: [TooltipComponent] }).compileComponents();
|
||||||
|
const fixture = TestBed.createComponent(TooltipComponent);
|
||||||
|
fixture.componentRef.setInput('message', 'Este producto no tiene stock disponible.');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
const trigger = element.querySelector('.tooltip-trigger');
|
||||||
|
const message = element.querySelector('.tooltip-message');
|
||||||
|
|
||||||
|
expect(trigger?.querySelector('i.fa-solid.fa-circle-info')).not.toBeNull();
|
||||||
|
expect(message?.textContent?.trim()).toBe('Este producto no tiene stock disponible.');
|
||||||
|
expect(trigger?.getAttribute('aria-describedby')).toBe(message?.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
14
src/app/shared/components/tooltip/tooltip.component.ts
Normal file
14
src/app/shared/components/tooltip/tooltip.component.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
let nextTooltipId = 0;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tooltip',
|
||||||
|
templateUrl: './tooltip.component.html',
|
||||||
|
styleUrl: './tooltip.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class TooltipComponent {
|
||||||
|
readonly message = input.required<string>();
|
||||||
|
protected readonly tooltipId = `app-tooltip-${nextTooltipId++}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user