diff --git a/.vscode/settings.json b/.vscode/settings.json index ad92582bd0..7c2feb7ef4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": false } 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..d5ae3ed396 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,27 +1,23 @@ +
-
- -
-
-
record.metadata.quality.details
- -
+ +
+ +
+
+ +
record.metadata.quality.details
+ +
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..8caca4af5e 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 { @@ -16,7 +17,11 @@ export default { component: MetadataQualityComponent, decorators: [ moduleMetadata({ - declarations: [ProgressBarComponent, MetadataQualityItemComponent], + declarations: [ + ProgressBarComponent, + PopoverComponent, + MetadataQualityItemComponent, + ], imports: [ CommonModule, MatIconModule, 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..14df78d338 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.spec.ts @@ -0,0 +1,41 @@ +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({ + declarations: [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() + const destroySpy = spyOn(component['tippyInstance'], 'destroy') + component.ngOnDestroy() + expect(destroySpy).toHaveBeenCalled() + }) +}) 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..f58285920a --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.stories.ts @@ -0,0 +1,49 @@ +import { Meta, Story } from '@storybook/angular' +import { PopoverComponent } from './popover.component' +import { moduleMetadata } from '@storybook/angular' + +export default { + title: 'Widgets/Popover', + component: PopoverComponent, + decorators: [ + moduleMetadata({ + declarations: [PopoverComponent], + }), + ], + 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..b26c4ec9f7 --- /dev/null +++ b/libs/ui/widgets/src/lib/popover/popover.component.ts @@ -0,0 +1,82 @@ +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'], +}) +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() + } + } +} diff --git a/libs/ui/widgets/src/lib/ui-widgets.module.ts b/libs/ui/widgets/src/lib/ui-widgets.module.ts index 726025db7c..eac2507679 100644 --- a/libs/ui/widgets/src/lib/ui-widgets.module.ts +++ b/libs/ui/widgets/src/lib/ui-widgets.module.ts @@ -12,11 +12,13 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner' import { MatIconModule } from '@angular/material/icon' import { SpinningLoaderComponent } from './spinning-loader/spinning-loader.component' import { CommonModule } from '@angular/common' +import { PopoverComponent } from './popover/popover.component' @NgModule({ declarations: [ ColorScaleComponent, ProgressBarComponent, + PopoverComponent, StepBarComponent, LoadingMaskComponent, SpinningLoaderComponent,