-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #729 from geonetwork/DH-read-more-description
[UI]: Add component max-lines with toggle
- Loading branch information
Showing
18 changed files
with
338 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,9 @@ | ||
import 'jest-preset-angular/setup-jest' | ||
import '../../../jest.setup' | ||
|
||
class ResizeObserverMock { | ||
observe = jest.fn() | ||
unobserve = jest.fn() | ||
} | ||
|
||
;(window as any).ResizeObserver = ResizeObserverMock |
Empty file.
15 changes: 15 additions & 0 deletions
15
libs/ui/elements/src/lib/max-lines/max-lines.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<div | ||
#container | ||
class="max-lines overflow-hidden transition-[max-height] duration-300" | ||
[ngClass]="isExpanded ? 'ease-in' : 'ease-out'" | ||
[style.maxHeight]="maxHeight" | ||
> | ||
<ng-content></ng-content> | ||
</div> | ||
<div | ||
*ngIf="showToggleButton" | ||
(click)="toggleDisplay()" | ||
class="text-secondary cursor-pointer pt-2.5" | ||
> | ||
{{ (isExpanded ? 'ui.readLess' : 'ui.readMore') | translate }} | ||
</div> |
122 changes: 122 additions & 0 deletions
122
libs/ui/elements/src/lib/max-lines/max-lines.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing' | ||
|
||
import { MaxLinesComponent } from './max-lines.component' | ||
import { Component, importProvidersFrom } from '@angular/core' | ||
import { TranslateModule } from '@ngx-translate/core' | ||
|
||
@Component({ | ||
template: ` | ||
<gn-ui-max-lines [maxLines]="maxLines"> | ||
<div class="test-content" style="height: 70px; max-height:80px;"> | ||
<p style="height: 40px;">Lorem ipsum dolor sit amet</p> | ||
</div> | ||
</gn-ui-max-lines> | ||
`, | ||
}) | ||
class TestHostComponent { | ||
maxLines: number | ||
} | ||
describe('MaxLinesComponent', () => { | ||
let fixture: ComponentFixture<TestHostComponent> | ||
let hostComponent: TestHostComponent | ||
let maxLinesComponent: MaxLinesComponent | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [TranslateModule.forRoot()], | ||
providers: [importProvidersFrom(TranslateModule.forRoot())], | ||
declarations: [MaxLinesComponent, TestHostComponent], | ||
}) | ||
fixture = TestBed.createComponent(TestHostComponent) | ||
hostComponent = fixture.componentInstance | ||
maxLinesComponent = fixture.debugElement.children[0].componentInstance | ||
}) | ||
|
||
it('should create', () => { | ||
fixture.detectChanges() | ||
expect(maxLinesComponent).toBeTruthy() | ||
}) | ||
|
||
it('should render content correctly', () => { | ||
hostComponent.maxLines = 10 | ||
fixture.detectChanges() | ||
|
||
const maxLinesElement: HTMLElement = | ||
fixture.nativeElement.querySelector('.max-lines') | ||
expect(maxLinesElement.childNodes[0].textContent).toBe( | ||
'Lorem ipsum dolor sit amet' | ||
) | ||
}) | ||
|
||
describe('should adjust maxHeight based on content height', () => { | ||
const contentHeight = 120 | ||
beforeEach(() => { | ||
jest | ||
.spyOn( | ||
fixture.nativeElement.querySelector('.test-content'), | ||
'getBoundingClientRect' | ||
) | ||
.mockReturnValueOnce({ | ||
left: 100, | ||
top: 50, | ||
right: 20, | ||
bottom: 10, | ||
x: 30, | ||
y: 40, | ||
widht: 150, | ||
height: contentHeight, | ||
}) | ||
}) | ||
it('use content height if content height is smaller than max space', () => { | ||
hostComponent.maxLines = 10 | ||
|
||
fixture.detectChanges() | ||
|
||
expect(maxLinesComponent.maxHeight).toBe(`${contentHeight}px`) | ||
}) | ||
|
||
it('use max space height if content height is bigger than max space', () => { | ||
hostComponent.maxLines = 2 | ||
|
||
const contentElement = | ||
fixture.nativeElement.querySelector('.test-content') | ||
fixture.detectChanges() | ||
|
||
const maxSpace = | ||
maxLinesComponent.getLineHeight(contentElement) * | ||
maxLinesComponent.maxLines | ||
|
||
expect(maxLinesComponent.maxHeight).toBe(`${maxSpace}px`) | ||
}) | ||
|
||
it('should show "Show More" button for long content', () => { | ||
hostComponent.maxLines = 2 | ||
|
||
fixture.detectChanges() | ||
|
||
expect(maxLinesComponent.showToggleButton).toBeTruthy() | ||
}) | ||
|
||
it('should toggle display when "Show More" button is clicked', () => { | ||
hostComponent.maxLines = 2 | ||
|
||
const contentElement = | ||
fixture.nativeElement.querySelector('.test-content') | ||
fixture.detectChanges() | ||
|
||
maxLinesComponent.toggleDisplay() | ||
|
||
expect(maxLinesComponent.isExpanded).toBeTruthy() | ||
expect(maxLinesComponent.maxHeight).toBe( | ||
`${ | ||
maxLinesComponent.maxLines * | ||
maxLinesComponent.getLineHeight(contentElement) | ||
}px` | ||
) | ||
}) | ||
}) | ||
|
||
afterEach(() => { | ||
fixture.destroy() | ||
}) | ||
}) |
40 changes: 40 additions & 0 deletions
40
libs/ui/elements/src/lib/max-lines/max-lines.component.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { | ||
applicationConfig, | ||
componentWrapperDecorator, | ||
Meta, | ||
moduleMetadata, | ||
StoryObj, | ||
} from '@storybook/angular' | ||
import { MaxLinesComponent } from './max-lines.component' | ||
|
||
export default { | ||
title: 'Elements/MaxLinesComponent', | ||
component: MaxLinesComponent, | ||
decorators: [ | ||
moduleMetadata({ | ||
declarations: [MaxLinesComponent], | ||
imports: [], | ||
}), | ||
applicationConfig({ | ||
providers: [], | ||
}), | ||
componentWrapperDecorator( | ||
(story) => `<div style="max-width: 800px">${story}</div>` | ||
), | ||
], | ||
} as Meta<MaxLinesComponent> | ||
|
||
const largeContent = `<div><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin purus elit, tincidunt et gravida sit amet, mattis eget orci. Suspendisse dignissim magna sed neque rutrum lobortis. Aenean vitae quam sapien. Phasellus eleifend tortor ac imperdiet tristique. Curabitur aliquet mauris tristique, iaculis est sit amet, pulvinar ipsum. Maecenas lacinia varius felis sit amet tempor. Curabitur pulvinar ipsum eros, quis accumsan odio hendrerit sit amet. | ||
Vestibulum placerat posuere lectus, sed lacinia orci sagittis consectetur. Duis eget eros consectetur, pretium nulla semper, pretium justo. Nullam facilisis maximus ipsum, a tempus erat eleifend non. Nulla nec lorem sed lorem porttitor ornare. Aliquam condimentum ante at laoreet dignissim. Vestibulum vel laoreet libero. Nam finibus augue ut ligula vulputate porta. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Nunc lorem nunc, interdum sed leo vel, vestibulum venenatis diam. Nam eget dignissim purus. Cras convallis leo sed porta tristique.</p></div>` | ||
|
||
export const Primary: StoryObj<MaxLinesComponent> = { | ||
args: { | ||
maxLines: 6, | ||
}, | ||
render: (args) => ({ | ||
template: `<div> | ||
<gn-ui-max-lines [maxLines]=${args.maxLines}>${largeContent}</gn-ui-max-lines> | ||
</div>`, | ||
}), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { | ||
Component, | ||
Input, | ||
ElementRef, | ||
ChangeDetectionStrategy, | ||
AfterViewInit, | ||
ViewChild, | ||
OnDestroy, | ||
ChangeDetectorRef, | ||
} from '@angular/core' | ||
|
||
@Component({ | ||
selector: 'gn-ui-max-lines', | ||
templateUrl: './max-lines.component.html', | ||
styleUrls: ['./max-lines.component.css'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class MaxLinesComponent implements AfterViewInit, OnDestroy { | ||
@Input() maxLines = 6 | ||
|
||
isExpanded = false | ||
maxHeight = '' | ||
showToggleButton = false | ||
observer: ResizeObserver | ||
|
||
@ViewChild('container') container!: ElementRef | ||
|
||
constructor(private cdr: ChangeDetectorRef) {} | ||
|
||
ngAfterViewInit() { | ||
this.calculateMaxHeight() | ||
|
||
this.observer = new ResizeObserver((mutations) => { | ||
mutations.forEach(() => { | ||
this.calculateMaxHeight() | ||
}) | ||
}) | ||
|
||
this.observer.observe(this.container.nativeElement.children[0]) | ||
} | ||
|
||
toggleDisplay() { | ||
this.isExpanded = !this.isExpanded | ||
this.calculateMaxHeight() | ||
} | ||
|
||
calculateMaxHeight() { | ||
const containerElement = this.container.nativeElement | ||
const contentElement = containerElement.children[0] | ||
const contentHeight = contentElement.getBoundingClientRect().height | ||
|
||
if (contentHeight) { | ||
if (contentHeight > this.maxLines * this.getLineHeight(contentElement)) { | ||
this.showToggleButton = true | ||
|
||
this.maxHeight = this.isExpanded | ||
? `${contentHeight}px` | ||
: `${this.maxLines * this.getLineHeight(contentElement)}px` | ||
} else { | ||
this.showToggleButton = false | ||
this.maxHeight = `${contentHeight}px` | ||
} | ||
containerElement.setAttribute( | ||
'style', | ||
`max-height: ${this.maxHeight}; overflow: hidden` | ||
) | ||
|
||
this.cdr.detectChanges() | ||
} | ||
} | ||
|
||
getLineHeight(element: HTMLElement): number { | ||
const computedStyle = window.getComputedStyle(element) | ||
const lineHeight = parseFloat(computedStyle.lineHeight) | ||
const fontSize = parseFloat(computedStyle.fontSize || '14') | ||
const result = isNaN(lineHeight) ? fontSize * 1.2 : lineHeight // Use a default if line height is not specified | ||
return result | ||
} | ||
|
||
ngOnDestroy(): void { | ||
this.observer.unobserve(this.container.nativeElement.children[0]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.