Skip to content

Commit

Permalink
Merge pull request #952 from gkeimeHDF/main
Browse files Browse the repository at this point in the history
Use a proper tooltip for the Metadata Quality widget
  • Loading branch information
Angi-Kinas authored Aug 1, 2024
2 parents 42a43a2 + e501f27 commit a1bcfe2
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 55 deletions.
5 changes: 5 additions & 0 deletions apps/demo/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="ml-4 flex flex-row">
<mat-icon class="material-symbols-outlined">{{ icon }}</mat-icon>
<mat-icon class="material-symbols-outlined min-w-fit">{{ icon }}</mat-icon>
<p class="ml-2 text">{{ labelKey | translate }}</p>
</div>
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
<div
*ngIf="metadataQualityDisplay"
class="mb-6 metadata-quality"
(mouseenter)="showMenu()"
(mouseleave)="hideMenu()"
>
<div class="min-w-[200px]" [class]="smaller ? 'leading-[8px]' : ''">
<gn-ui-progress-bar
(focus)="showMenu()"
(blur)="hideMenu()"
tabindex="0"
[value]="qualityScore"
type="primary"
></gn-ui-progress-bar>
</div>
<div
class="absolute z-10 bg-white border border-black border-opacity-35 rounded-lg shadow-lg p-5 whitespace-nowrap"
[class]="isMenuShown ? 'block' : 'hidden'"
>
<div *ngIf="metadataQualityDisplay" class="mb-6 metadata-quality">
<gn-ui-popover [content]="popoverItems" theme="light-border">
<div class="min-w-[200px]" [class]="smaller ? 'leading-[8px]' : ''">
<gn-ui-progress-bar
tabindex="0"
[value]="qualityScore"
type="primary"
></gn-ui-progress-bar>
</div>
</gn-ui-popover>
</div>
<ng-template #popoverItems>
<div class="p-2 py-4">
<div class="mb-4 font-bold" translate>record.metadata.quality.details</div>
<gn-ui-metadata-quality-item
*ngFor="let e of items"
[name]="e.name"
[value]="e.value"
></gn-ui-metadata-quality-item>
</div>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -44,6 +47,7 @@ describe('MetadataQualityComponent', () => {
MatIconModule,
UtilI18nModule,
TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG),
PopoverComponent,
],
}).compileComponents()
})
Expand All @@ -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('[email protected]')
})
Expand All @@ -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)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -22,6 +23,7 @@ export default {
MatIconModule,
UtilI18nModule,
TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG),
PopoverComponent,
],
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 })
Expand Down
3 changes: 2 additions & 1 deletion libs/ui/elements/src/lib/ui-elements.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ContentGhostComponent } from './content-ghost/content-ghost.component'
import { DownloadItemComponent } from './download-item/download-item.component'
import { DownloadsListComponent } from './downloads-list/downloads-list.component'
import { ApiCardComponent } from './api-card/api-card.component'
import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets'
import { PopoverComponent, UiWidgetsModule } from '@geonetwork-ui/ui/widgets'
import { MaxLinesComponent, UiLayoutModule } from '@geonetwork-ui/ui/layout'
import { TranslateModule } from '@ngx-translate/core'
import { RelatedRecordCardComponent } from './related-record-card/related-record-card.component'
Expand Down Expand Up @@ -45,6 +45,7 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
UiInputsModule,
FormsModule,
NgOptimizedImage,
PopoverComponent,
MarkdownParserComponent,
ThumbnailComponent,
TimeSincePipe,
Expand Down
1 change: 1 addition & 0 deletions libs/ui/widgets/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions libs/ui/widgets/src/lib/popover/popover.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span #popoverContent>
<ng-content></ng-content>
</span>
44 changes: 44 additions & 0 deletions libs/ui/widgets/src/lib/popover/popover.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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<PopoverComponent>

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)
})
})
50 changes: 50 additions & 0 deletions libs/ui/widgets/src/lib/popover/popover.component.stories.ts
Original file line number Diff line number Diff line change
@@ -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<PopoverComponent> = (args: PopoverComponent) => ({
component: PopoverComponent,
props: args,
template: `<gn-ui-popover [content]="content" [theme]="theme">Hover me to see tooltip</gn-ui-popover>`,
})

export const Default = Template.bind({})
Default.args = {
content: 'Default tooltip content',
theme: '',
}

export const TemplateContent: Story<PopoverComponent> = (
args: PopoverComponent

Check warning on line 36 in libs/ui/widgets/src/lib/popover/popover.component.stories.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

'args' is defined but never used
) => ({
component: PopoverComponent,
template: `
<ng-template #popoverTemplate>
<div>
<strong>Tooltip Header</strong>
<p>Detailed information about the tooltip.</p>
</div>
</ng-template>
<gn-ui-popover [content]="popoverTemplate" [theme]="theme">
Hover me to see tooltip
</gn-ui-popover>
`,
})
85 changes: 85 additions & 0 deletions libs/ui/widgets/src/lib/popover/popover.component.ts
Original file line number Diff line number Diff line change
@@ -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<any>

Check warning on line 27 in libs/ui/widgets/src/lib/popover/popover.component.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

Unexpected any. Specify a different type
@Input() theme: 'light' | 'light-border' | 'translucent' | 'material' | ''

private tippyInstance: Instance
private view: EmbeddedViewRef<any>

Check warning on line 31 in libs/ui/widgets/src/lib/popover/popover.component.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

Unexpected any. Specify a different type

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()
}
}
}

0 comments on commit a1bcfe2

Please sign in to comment.