Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a proper tooltip for the Metadata Quality widget #952

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please make this component standalone? this is what we do for every new component now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is standalone component, and what does it apply ? i had based my code on ProgressBarComponent which is not standalone

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jahow I have done it yersterday, and i fixed the lint error this morning should be ok now

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' | ''
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow the theming doesn't seem to get applied. Not in the storybook nor in the datahub. Is there something I'm missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my screenshoot come from the story-book using light-border. Your screenshoot looks like the default theming same as ''. I don't know yet why it doesn't apply on your side. Which browser do you use?
I had added only light-border on datahub and the differents on storybook.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the theming works on my side as well. Not sure what was the issue. Never mind! Looks good to me.


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