Browse Source

Add copy-to-clipboard functionality to value component

pull/6575/head
Erwin-N 2 weeks ago
committed by Thomas Kaul
parent
commit
001bce8605
  1. 27
      libs/ui/src/lib/value/value.component.html
  2. 4
      libs/ui/src/lib/value/value.component.scss
  3. 26
      libs/ui/src/lib/value/value.component.stories.ts
  4. 65
      libs/ui/src/lib/value/value.component.ts

27
libs/ui/src/lib/value/value.component.html

@ -4,7 +4,23 @@
</div> </div>
} }
<div class="d-flex flex-column w-100"> <div class="d-flex flex-column w-100">
<ng-template #label><ng-content /></ng-template> <ng-template #label
><span #labelContent>
<ng-content></ng-content>
</span>
</ng-template>
<ng-template #copyIcon>
@if (enableCopyToClipboardButton) {
<a
class="cursor-pointer"
ng-if="label"
(click)="onCopyValueToClipboard()"
>
<ion-icon class="ml-1 mdc-copy" name="copy-outline" role="img" />
</a>
}
</ng-template>
@if (value || value === 0 || value === null) { @if (value || value === 0 || value === null) {
<div <div
class="align-items-center d-flex" class="align-items-center d-flex"
@ -71,6 +87,9 @@
{{ formattedValue }} {{ formattedValue }}
</div> </div>
} }
@if (!hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
</div> </div>
} }
@ -88,6 +107,9 @@
@if (size === 'large') { @if (size === 'large') {
<div class="text-truncate"> <div class="text-truncate">
<span class="h6"><ng-container *ngTemplateOutlet="label" /></span> <span class="h6"><ng-container *ngTemplateOutlet="label" /></span>
@if (hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
@if (subLabel) { @if (subLabel) {
<span class="text-muted"> {{ subLabel }}</span> <span class="text-muted"> {{ subLabel }}</span>
} }
@ -95,6 +117,9 @@
} @else { } @else {
<small class="d-block text-truncate"> <small class="d-block text-truncate">
<ng-container *ngTemplateOutlet="label" /> <ng-container *ngTemplateOutlet="label" />
@if (hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
</small> </small>
} }
</div> </div>

4
libs/ui/src/lib/value/value.component.scss

@ -7,4 +7,8 @@
font-variant-numeric: initial; font-variant-numeric: initial;
line-height: 1; line-height: 1;
} }
.mdc-copy {
display: inline-flex;
vertical-align: middle;
}
} }

26
libs/ui/src/lib/value/value.component.stories.ts

@ -32,6 +32,32 @@ export const Loading: Story = {
} }
}; };
export const IsinNoLabel: Story = {
args: {
isCurrency: false,
locale: 'en-US',
unit: 'USD',
value: 'US5949181045',
enableCopyToClipboardButton: true
},
name: 'Without Label with Copy'
};
export const IsinWithLabel: Story = {
args: {
isCurrency: false,
locale: 'en-US',
unit: 'USD',
value: 'US5949181045',
enableCopyToClipboardButton: true
},
render: (args) => ({
props: args,
template: `<gf-value [locale]="locale" [size]="size" [value]="value">ISIN</gf-value>`
}),
name: 'With Label and Copy'
};
export const Currency: Story = { export const Currency: Story = {
args: { args: {
isCurrency: true, isCurrency: true,

65
libs/ui/src/lib/value/value.component.ts

@ -1,17 +1,25 @@
import { getLocale } from '@ghostfolio/common/helper'; import { getLocale } from '@ghostfolio/common/helper';
import { Clipboard } from '@angular/cdk/clipboard';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
CUSTOM_ELEMENTS_SCHEMA, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
ElementRef,
input,
Input, Input,
OnChanges, OnChanges,
computed, ViewChild
input
} from '@angular/core'; } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { copyOutline } from 'ionicons/icons';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import ms from 'ms';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({ @Component({
@ -22,9 +30,10 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
styleUrls: ['./value.component.scss'], styleUrls: ['./value.component.scss'],
templateUrl: './value.component.html' templateUrl: './value.component.html'
}) })
export class GfValueComponent implements OnChanges { export class GfValueComponent implements AfterViewInit, OnChanges {
@Input() colorizeSign = false; @Input() colorizeSign = false;
@Input() deviceType: string; @Input() deviceType: string;
@Input() enableCopyToClipboardButton = false;
@Input() icon = ''; @Input() icon = '';
@Input() isAbsolute = false; @Input() isAbsolute = false;
@Input() isCurrency = false; @Input() isCurrency = false;
@ -37,12 +46,24 @@ export class GfValueComponent implements OnChanges {
@Input() unit = ''; @Input() unit = '';
@Input() value: number | string = ''; @Input() value: number | string = '';
@ViewChild('labelContent', { static: false }) labelContent!: ElementRef;
public absoluteValue = 0; public absoluteValue = 0;
public formattedValue = ''; public formattedValue = '';
public hasLabel = false;
public isNumber = false; public isNumber = false;
public isString = false; public isString = false;
public useAbsoluteValue = false; public useAbsoluteValue = false;
public constructor(
private clipboard: Clipboard,
private snackBar: MatSnackBar
) {
addIcons({
copyOutline
});
}
public readonly precision = input<number>(); public readonly precision = input<number>();
private readonly formatOptions = computed<Intl.NumberFormatOptions>(() => { private readonly formatOptions = computed<Intl.NumberFormatOptions>(() => {
@ -59,6 +80,20 @@ export class GfValueComponent implements OnChanges {
return precision !== undefined && precision >= 0; return precision !== undefined && precision >= 0;
} }
private initializeVariables() {
this.absoluteValue = 0;
this.formattedValue = '';
this.isNumber = false;
this.isString = false;
this.locale = this.locale || getLocale();
this.useAbsoluteValue = false;
}
public ngAfterViewInit() {
const el = this.labelContent.nativeElement;
this.hasLabel = el.textContent.trim().length > 0 || el.children.length > 0;
}
public ngOnChanges() { public ngOnChanges() {
this.initializeVariables(); this.initializeVariables();
@ -137,12 +172,20 @@ export class GfValueComponent implements OnChanges {
} }
} }
private initializeVariables() { public onCopyValueToClipboard() {
this.absoluteValue = 0; if (this.value || this.value === 0) {
this.formattedValue = ''; this.clipboard.copy(this.formattedValue);
this.isNumber = false; this.snackBar.open(
this.isString = false; '✅ ' + $localize`Value has been copied to the clipboard`,
this.locale = this.locale || getLocale(); undefined,
this.useAbsoluteValue = false; {
duration: ms('3 seconds')
}
);
} else {
this.snackBar.open($localize`Value is empty`, undefined, {
duration: ms('3 seconds')
});
}
} }
} }

Loading…
Cancel
Save