mirror of https://github.com/ghostfolio/ghostfolio
committed by
Thomas
10 changed files with 258 additions and 2 deletions
@ -0,0 +1,6 @@ |
|||
describe('ui', () => { |
|||
beforeEach(() => cy.visit('/iframe.html?id=valuecomponent--loading')); |
|||
it('should render the component', () => { |
|||
cy.get('gf-value').should('exist'); |
|||
}); |
|||
}); |
@ -1,7 +1,15 @@ |
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { ValueComponent } from './value/value.component'; |
|||
// import { GfValueModule } from './value/value.module';
|
|||
|
|||
@NgModule({ |
|||
imports: [CommonModule] |
|||
imports: [CommonModule/*, GfValueModule*/], |
|||
declarations: [ |
|||
ValueComponent |
|||
], |
|||
exports: [ |
|||
ValueComponent |
|||
] |
|||
}) |
|||
export class UiModule {} |
|||
|
@ -0,0 +1,46 @@ |
|||
<ng-container *ngIf="value || value === 0 || value === null"> |
|||
<div |
|||
class="d-flex" |
|||
[ngClass]="position === 'end' ? 'justify-content-end' : ''" |
|||
> |
|||
<ng-container *ngIf="isNumber || value === null"> |
|||
<div *ngIf="colorizeSign && value > 0" class="mr-1 text-success">+</div> |
|||
<div *ngIf="colorizeSign && value < 0" class="mr-1 text-danger">-</div> |
|||
<div *ngIf="isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> |
|||
{{ formattedValue }}% |
|||
</div> |
|||
<div *ngIf="!isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> |
|||
<ng-container *ngIf="value === null"> |
|||
<span class="text-monospace text-muted">***</span> |
|||
</ng-container> |
|||
<ng-container *ngIf="value !== null"> |
|||
{{ formattedValue }} |
|||
</ng-container> |
|||
</div> |
|||
<small *ngIf="currency && size === 'medium'" class="ml-1"> |
|||
{{ currency }} |
|||
</small> |
|||
<div *ngIf="currency && size !== 'medium'" class="ml-1"> |
|||
{{ currency }} |
|||
</div> |
|||
</ng-container> |
|||
<ng-container *ngIf="isDate"> |
|||
<div [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> |
|||
{{ formattedDate }} |
|||
</div> |
|||
</ng-container> |
|||
</div> |
|||
<small *ngIf="label"> |
|||
{{ label }} |
|||
</small> |
|||
</ng-container> |
|||
|
|||
<ngx-skeleton-loader |
|||
*ngIf="value === undefined" |
|||
animation="pulse" |
|||
[theme]="{ |
|||
height: '1.5rem', |
|||
width: '5rem' |
|||
}" |
|||
></ngx-skeleton-loader> |
|||
|
@ -0,0 +1,5 @@ |
|||
:host { |
|||
display: flex; |
|||
flex-direction: column; |
|||
font-variant-numeric: tabular-nums; |
|||
} |
@ -0,0 +1,25 @@ |
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { ValueComponent } from './value.component'; |
|||
|
|||
describe('ValueComponent', () => { |
|||
let component: ValueComponent; |
|||
let fixture: ComponentFixture<ValueComponent>; |
|||
|
|||
beforeEach(async () => { |
|||
await TestBed.configureTestingModule({ |
|||
declarations: [ ValueComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
}); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(ValueComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,38 @@ |
|||
import { moduleMetadata, Story, Meta } from '@storybook/angular'; |
|||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; |
|||
import { ValueComponent } from './value.component'; |
|||
|
|||
export default { |
|||
title: 'Value', |
|||
component: ValueComponent, |
|||
decorators: [ |
|||
moduleMetadata({ |
|||
imports: [NgxSkeletonLoaderModule], |
|||
}) |
|||
], |
|||
} as Meta<ValueComponent>; |
|||
|
|||
const Template: Story<ValueComponent> = (args: ValueComponent) => ({ |
|||
props: args, |
|||
}); |
|||
|
|||
export const Loading = Template.bind({}); |
|||
Loading.args = { |
|||
value: undefined |
|||
} |
|||
|
|||
export const Integer = Template.bind({}); |
|||
Integer.args = { |
|||
isInteger: true, |
|||
locale: 'en', |
|||
value: 7 |
|||
} |
|||
|
|||
export const Currency = Template.bind({}); |
|||
Currency.args = { |
|||
currency: 'USD', |
|||
isInteger: true, |
|||
label: 'Label', |
|||
locale: 'en', |
|||
value: 7 |
|||
} |
@ -0,0 +1,108 @@ |
|||
import { |
|||
ChangeDetectionStrategy, |
|||
Component, |
|||
Input, |
|||
OnChanges, |
|||
} from '@angular/core'; |
|||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; |
|||
import { format, isDate } from 'date-fns'; |
|||
import { isNumber } from 'lodash'; |
|||
|
|||
@Component({ |
|||
selector: 'gf-value', |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
templateUrl: './value.component.html', |
|||
styleUrls: ['./value.component.scss'] |
|||
}) |
|||
export class ValueComponent implements OnChanges { |
|||
@Input() colorizeSign = false; |
|||
@Input() currency = ''; |
|||
@Input() isCurrency = false; |
|||
@Input() isInteger = false; |
|||
@Input() isPercent = false; |
|||
@Input() label = ''; |
|||
@Input() locale = ''; |
|||
@Input() position = ''; |
|||
@Input() size = ''; |
|||
@Input() value: number | string = ''; |
|||
|
|||
public absoluteValue = 0; |
|||
public formattedDate = ''; |
|||
public formattedValue = ''; |
|||
public isDate = false; |
|||
public isNumber = false; |
|||
public useAbsoluteValue = false; |
|||
|
|||
public constructor() {} |
|||
|
|||
public ngOnChanges() { |
|||
if (this.value || this.value === 0) { |
|||
if (isNumber(this.value)) { |
|||
this.isDate = false; |
|||
this.isNumber = true; |
|||
this.absoluteValue = Math.abs(<number>this.value); |
|||
|
|||
if (this.colorizeSign) { |
|||
this.useAbsoluteValue = true; |
|||
if (this.currency || this.isCurrency) { |
|||
try { |
|||
this.formattedValue = this.absoluteValue.toLocaleString( |
|||
this.locale, |
|||
{ |
|||
maximumFractionDigits: 2, |
|||
minimumFractionDigits: 2 |
|||
} |
|||
); |
|||
} catch {} |
|||
} else if (this.isPercent) { |
|||
try { |
|||
this.formattedValue = (this.absoluteValue * 100).toLocaleString( |
|||
this.locale, |
|||
{ |
|||
maximumFractionDigits: 2, |
|||
minimumFractionDigits: 2 |
|||
} |
|||
); |
|||
} catch {} |
|||
} |
|||
} else if (this.isPercent) { |
|||
try { |
|||
this.formattedValue = (this.value * 100).toLocaleString( |
|||
this.locale, |
|||
{ |
|||
maximumFractionDigits: 2, |
|||
minimumFractionDigits: 2 |
|||
} |
|||
); |
|||
} catch {} |
|||
} else if (this.currency || this.isCurrency) { |
|||
try { |
|||
this.formattedValue = this.value?.toLocaleString(this.locale, { |
|||
maximumFractionDigits: 2, |
|||
minimumFractionDigits: 2 |
|||
}); |
|||
} catch {} |
|||
} else if (this.isInteger) { |
|||
try { |
|||
this.formattedValue = this.value?.toLocaleString(this.locale, { |
|||
maximumFractionDigits: 0, |
|||
minimumFractionDigits: 0 |
|||
}); |
|||
} catch {} |
|||
} |
|||
} else { |
|||
try { |
|||
if (isDate(new Date(this.value))) { |
|||
this.isDate = true; |
|||
this.isNumber = false; |
|||
|
|||
this.formattedDate = format( |
|||
new Date(<string>this.value), |
|||
DEFAULT_DATE_FORMAT |
|||
); |
|||
} |
|||
} catch {} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; |
|||
|
|||
import { ValueComponent } from './value.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ValueComponent], |
|||
exports: [ValueComponent], |
|||
imports: [CommonModule, NgxSkeletonLoaderModule], |
|||
providers: [] |
|||
}) |
|||
export class GfValueModule {} |
Loading…
Reference in new issue