From 62a5751c86a10afc570686dfe4b1f827c9be616c Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 29 Jul 2024 14:43:20 +0200 Subject: [PATCH] fix MEL use tippy & fix Angi-Kinas & fix jahow --- apps/demo/project.json | 5 ++ .../metadata-quality-item.component.html | 2 +- .../metadata-quality.component.html | 34 +++----- .../metadata-quality.component.spec.ts | 33 +++---- .../metadata-quality.component.stories.ts | 2 + .../metadata-quality.component.ts | 10 --- libs/ui/widgets/src/index.ts | 1 + .../src/lib/popover/popover.component.css | 0 .../src/lib/popover/popover.component.html | 3 + .../src/lib/popover/popover.component.spec.ts | 44 ++++++++++ .../lib/popover/popover.component.stories.ts | 50 +++++++++++ .../src/lib/popover/popover.component.ts | 85 +++++++++++++++++++ 12 files changed, 215 insertions(+), 54 deletions(-) create mode 100644 libs/ui/widgets/src/lib/popover/popover.component.css create mode 100644 libs/ui/widgets/src/lib/popover/popover.component.html create mode 100644 libs/ui/widgets/src/lib/popover/popover.component.spec.ts create mode 100644 libs/ui/widgets/src/lib/popover/popover.component.stories.ts create mode 100644 libs/ui/widgets/src/lib/popover/popover.component.ts diff --git a/apps/demo/project.json b/apps/demo/project.json index d6a3bf225f..b22e02d35c 100644 --- a/apps/demo/project.json +++ b/apps/demo/project.json @@ -25,6 +25,11 @@ } ], "styles": [ + "node_modules/tippy.js/dist/tippy.css", + "node_modules/tippy.js/themes/light.css", + "node_modules/tippy.js/themes/light-border.css", + "node_modules/tippy.js/themes/material.css", + "node_modules/tippy.js/themes/translucent.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "apps/demo/src/styles.css", "tailwind.base.css" diff --git a/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html index 09036c9b6c..397f7b8eda 100644 --- a/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html +++ b/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html @@ -1,4 +1,4 @@
- {{ icon }} + {{ icon }}

{{ labelKey | translate }}

diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html index d56a5c1067..392a4d748c 100644 --- a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html @@ -1,22 +1,16 @@ -
-
- -
-
+ + +
record.metadata.quality.details
-
+ diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts index 0624689541..2b226cfd04 100644 --- a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.spec.ts @@ -9,7 +9,10 @@ import { } from '@geonetwork-ui/util/i18n' import { TranslateModule } from '@ngx-translate/core' import { MetadataQualityItemComponent } from '../metadata-quality-item/metadata-quality-item.component' -import { ProgressBarComponent } from '@geonetwork-ui/ui/widgets' +import { + PopoverComponent, + ProgressBarComponent, +} from '@geonetwork-ui/ui/widgets' import { UtilSharedModule } from '@geonetwork-ui/util/shared' import { By } from '@angular/platform-browser' import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' @@ -44,6 +47,7 @@ describe('MetadataQualityComponent', () => { MatIconModule, UtilI18nModule, TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + PopoverComponent, ], }).compileComponents() }) @@ -61,28 +65,6 @@ describe('MetadataQualityComponent', () => { expect(component).toBeTruthy() }) - it('focus should show menu / blur should hide', () => { - const progressBar = fixture.debugElement.query(By.css('gn-ui-progress-bar')) - progressBar.nativeElement.focus() - expect(component.isMenuShown).toBe(true) - progressBar.nativeElement.blur() - expect(component.isMenuShown).toBe(false) - }) - - it('mouseenter should show menu / mouseleave should hide', () => { - const metadataQuality = fixture.debugElement.query( - By.css('.metadata-quality') - ) - - const mouseEnterEvent = new Event('mouseenter') - metadataQuality.nativeElement.dispatchEvent(mouseEnterEvent) - expect(component.isMenuShown).toBe(true) - - const mouseLeaveEvent = new Event('mouseleave') - metadataQuality.nativeElement.dispatchEvent(mouseLeaveEvent) - expect(component.isMenuShown).toBe(false) - }) - it('content', () => { expect(component.metadata?.contacts[0]?.email).toBe('bob@org.net') }) @@ -94,6 +76,11 @@ describe('MetadataQualityComponent', () => { }) it('should display sub-components with correct inputs', () => { + const popoverElement = fixture.debugElement.query( + By.directive(PopoverComponent) + ) + popoverElement.triggerEventHandler('mouseenter', null) + fixture.detectChanges() const metadataItems = fixture.debugElement.queryAll( By.directive(MetadataQualityItemComponent) ) diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts index cf67b7a402..6b22590dcb 100644 --- a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.stories.ts @@ -9,6 +9,7 @@ import { import { TranslateModule } from '@ngx-translate/core' import { MetadataQualityItemComponent } from '../metadata-quality-item/metadata-quality-item.component' import { ProgressBarComponent } from '@geonetwork-ui/ui/widgets' +import { PopoverComponent } from '@geonetwork-ui/ui/widgets' import { MatIconModule } from '@angular/material/icon' export default { @@ -22,6 +23,7 @@ export default { MatIconModule, UtilI18nModule, TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + PopoverComponent, ], }), ], diff --git a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts index e6d86036e1..6f604eb608 100644 --- a/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +++ b/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts @@ -21,8 +21,6 @@ export class MetadataQualityComponent implements OnChanges { items: MetadataQualityItem[] = [] - isMenuShown = false - get qualityScore() { const qualityScore = this.metadata?.extras?.qualityScore return typeof qualityScore === 'number' @@ -36,14 +34,6 @@ export class MetadataQualityComponent implements OnChanges { ) } - showMenu() { - this.isMenuShown = true - } - - hideMenu() { - this.isMenuShown = false - } - private add(name: string, value: boolean) { if (this.metadataQualityDisplay?.[name] !== false) { this.items.push({ name, value }) diff --git a/libs/ui/widgets/src/index.ts b/libs/ui/widgets/src/index.ts index 26b0aef91e..3100b6e6e8 100644 --- a/libs/ui/widgets/src/index.ts +++ b/libs/ui/widgets/src/index.ts @@ -1,5 +1,6 @@ export * from './lib/ui-widgets.module' export * from './lib/progress-bar/progress-bar.component' +export * from './lib/popover/popover.component' export * from './lib/loading-mask/loading-mask.component' export * from './lib/color-scale/color-scale.component' export * from './lib/popup-alert/popup-alert.component' diff --git a/libs/ui/widgets/src/lib/popover/popover.component.css b/libs/ui/widgets/src/lib/popover/popover.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/ui/widgets/src/lib/popover/popover.component.html b/libs/ui/widgets/src/lib/popover/popover.component.html new file mode 100644 index 0000000000..08a008c9e8 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.html @@ -0,0 +1,3 @@ + + + diff --git a/libs/ui/widgets/src/lib/popover/popover.component.spec.ts b/libs/ui/widgets/src/lib/popover/popover.component.spec.ts new file mode 100644 index 0000000000..5ad2ac6433 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.spec.ts @@ -0,0 +1,44 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { PopoverComponent } from './popover.component' +import { ElementRef } from '@angular/core' + +describe('PopoverComponent', () => { + let component: PopoverComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PopoverComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(PopoverComponent) + component = fixture.componentInstance + component.content = 'Test tooltip content' + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should initialize tippy instance on view init', () => { + const elementRef = new ElementRef(document.createElement('div')) + component.popoverContent = elementRef + component.ngAfterViewInit() + expect(component['tippyInstance']).toBeDefined() + }) + + it('should destroy tippy instance on destroy', () => { + const elementRef = new ElementRef(document.createElement('div')) + component.popoverContent = elementRef + component.ngAfterViewInit() + let destroyCalled = false + component['tippyInstance'].destroy = () => { + destroyCalled = true + } + component.ngOnDestroy() + expect(destroyCalled).toBe(true) + }) +}) diff --git a/libs/ui/widgets/src/lib/popover/popover.component.stories.ts b/libs/ui/widgets/src/lib/popover/popover.component.stories.ts new file mode 100644 index 0000000000..e56a2ec554 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.stories.ts @@ -0,0 +1,50 @@ +import { Meta, Story } from '@storybook/angular' +import { PopoverComponent } from './popover.component' +import { moduleMetadata } from '@storybook/angular' +import { CommonModule } from '@angular/common' + +export default { + title: 'Widgets/Popover', + component: PopoverComponent, + decorators: [ + moduleMetadata({ + imports: [CommonModule], + }), + ], + argTypes: { + content: { control: 'text' }, + theme: { + control: 'select', + options: ['', 'light', 'light-border', 'translucent', 'material'], + }, + }, +} as Meta + +const Template: Story = (args: PopoverComponent) => ({ + component: PopoverComponent, + props: args, + template: `Hover me to see tooltip`, +}) + +export const Default = Template.bind({}) +Default.args = { + content: 'Default tooltip content', + theme: '', +} + +export const TemplateContent: Story = ( + args: PopoverComponent +) => ({ + component: PopoverComponent, + template: ` + +
+ Tooltip Header +

Detailed information about the tooltip.

+
+
+ + Hover me to see tooltip + + `, +}) diff --git a/libs/ui/widgets/src/lib/popover/popover.component.ts b/libs/ui/widgets/src/lib/popover/popover.component.ts new file mode 100644 index 0000000000..a167101c03 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.ts @@ -0,0 +1,85 @@ +import { CommonModule } from '@angular/common' +import { + Component, + AfterViewInit, + ElementRef, + Input, + ViewChild, + OnDestroy, + OnChanges, + SimpleChanges, + TemplateRef, + Renderer2, + ViewContainerRef, + EmbeddedViewRef, +} from '@angular/core' +import tippy, { Instance } from 'tippy.js' + +@Component({ + selector: 'gn-ui-popover', + templateUrl: './popover.component.html', + styleUrls: ['./popover.component.css'], + standalone: true, + imports: [CommonModule], +}) +export class PopoverComponent implements AfterViewInit, OnChanges, OnDestroy { + @ViewChild('popoverContent', { static: false }) popoverContent: ElementRef + @Input() content: string | TemplateRef + @Input() theme: 'light' | 'light-border' | 'translucent' | 'material' | '' + + private tippyInstance: Instance + private view: EmbeddedViewRef + + constructor( + private viewContainerRef: ViewContainerRef, + private renderer: Renderer2 + ) {} + + private getContent(): string | HTMLElement { + if (this.content instanceof TemplateRef) { + if (this.view) { + this.view.destroy() + } + this.view = this.viewContainerRef.createEmbeddedView(this.content) + this.view.detectChanges() + const wrapper = this.renderer.createElement('div') // Create a wrapper div + this.view.rootNodes.forEach((node) => { + this.renderer.appendChild(wrapper, node) // Append each root node to the wrapper + }) + return wrapper + } + return this.content + } + + ngAfterViewInit(): void { + this.tippyInstance = tippy(this.popoverContent.nativeElement as Element, { + content: this.getContent(), + allowHTML: true, + theme: this.theme, + }) + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['theme']) { + this.theme = changes['theme'].currentValue + if (this.tippyInstance) { + this.tippyInstance.setProps({ theme: this.theme }) + } + } + if (changes['content']) { + this.content = changes['content'].currentValue + if (this.tippyInstance) { + this.tippyInstance.setContent(this.getContent()) + } + } + } + + ngOnDestroy(): void { + if (this.tippyInstance) { + this.tippyInstance.destroy() + } + if (this.view) { + this.view.destroy() + } + } +}