Browse Source

Feature/add copy-to-clipboard functionality to value component (#6575)

* Add copy-to-clipboard functionality

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/5018/merge
Erwin 1 day ago
committed by GitHub
parent
commit
fda0c3afc8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 4
      apps/client/src/styles.scss
  3. 30
      libs/ui/src/lib/value/value.component.html
  4. 6
      libs/ui/src/lib/value/value.component.stories.ts
  5. 58
      libs/ui/src/lib/value/value.component.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added support for a copy-to-clipboard functionality in the value component
### Changed
- Improved the language localization for Spanish (`es`)

4
apps/client/src/styles.scss

@ -546,6 +546,10 @@ ngx-skeleton-loader {
padding: 9px 24px !important;
}
.no-height {
height: unset !important;
}
.no-min-width {
min-width: unset !important;
}

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

@ -4,7 +4,26 @@
</div>
}
<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) {
<button
class="ml-1 no-height no-min-width p-1"
i18n-title
mat-button
title="Copy to clipboard"
type="button"
(click)="onCopyValueToClipboard()"
>
<ion-icon class="text-muted" name="copy-outline" />
</button>
}
</ng-template>
@if (value || value === 0 || value === null) {
<div
class="align-items-center d-flex"
@ -71,6 +90,9 @@
{{ formattedValue }}
</div>
}
@if (!hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
</div>
}
@ -88,6 +110,9 @@
@if (size === 'large') {
<div class="text-truncate">
<span class="h6"><ng-container *ngTemplateOutlet="label" /></span>
@if (hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
@if (subLabel) {
<span class="text-muted"> {{ subLabel }}</span>
}
@ -95,6 +120,9 @@
} @else {
<small class="d-block text-truncate">
<ng-container *ngTemplateOutlet="label" />
@if (hasLabel) {
<ng-container *ngTemplateOutlet="copyIcon" />
}
</small>
}
</div>

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

@ -1,3 +1,4 @@
import '@angular/localize/init';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -17,6 +18,9 @@ export default {
control: 'select',
options: ['desktop', 'mobile']
},
enableCopyToClipboardButton: {
control: 'boolean'
},
size: {
control: 'select',
options: ['small', 'medium', 'large']
@ -58,7 +62,7 @@ export const Label: Story = {
},
render: (args) => ({
props: args,
template: `<gf-value [locale]="locale" [size]="size" [value]="value">Label</gf-value>`
template: `<gf-value [enableCopyToClipboardButton]="enableCopyToClipboardButton" [locale]="locale" [size]="size" [value]="value">Label</gf-value>`
})
};

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

@ -1,30 +1,41 @@
import { getLocale } from '@ghostfolio/common/helper';
import { Clipboard } from '@angular/cdk/clipboard';
import { CommonModule } from '@angular/common';
import {
CUSTOM_ELEMENTS_SCHEMA,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
ElementRef,
input,
Input,
OnChanges,
computed,
input
ViewChild
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatSnackBar } from '@angular/material/snack-bar';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { copyOutline } from 'ionicons/icons';
import { isNumber } from 'lodash';
import ms from 'ms';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, IonIcon, NgxSkeletonLoaderModule],
imports: [CommonModule, IonIcon, MatButtonModule, NgxSkeletonLoaderModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-value',
styleUrls: ['./value.component.scss'],
templateUrl: './value.component.html'
})
export class GfValueComponent implements OnChanges {
export class GfValueComponent implements AfterViewInit, OnChanges {
@Input() colorizeSign = false;
@Input() deviceType: string;
@Input() enableCopyToClipboardButton = false;
@Input() icon = '';
@Input() isAbsolute = false;
@Input() isCurrency = false;
@ -37,12 +48,26 @@ export class GfValueComponent implements OnChanges {
@Input() unit = '';
@Input() value: number | string = '';
@ViewChild('labelContent', { static: false })
labelContent!: ElementRef<HTMLSpanElement>;
public absoluteValue = 0;
public formattedValue = '';
public hasLabel = false;
public isNumber = false;
public isString = false;
public useAbsoluteValue = false;
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private clipboard: Clipboard,
private snackBar: MatSnackBar
) {
addIcons({
copyOutline
});
}
public readonly precision = input<number>();
private readonly formatOptions = computed<Intl.NumberFormatOptions>(() => {
@ -59,6 +84,17 @@ export class GfValueComponent implements OnChanges {
return precision !== undefined && precision >= 0;
}
public ngAfterViewInit() {
if (this.labelContent) {
const element = this.labelContent.nativeElement;
this.hasLabel =
element.children.length > 0 || element.textContent.trim().length > 0;
this.changeDetectorRef.markForCheck();
}
}
public ngOnChanges() {
this.initializeVariables();
@ -137,6 +173,18 @@ export class GfValueComponent implements OnChanges {
}
}
public onCopyValueToClipboard() {
this.clipboard.copy(String(this.value));
this.snackBar.open(
'✅ ' + $localize`${this.value} has been copied to the clipboard`,
undefined,
{
duration: ms('3 seconds')
}
);
}
private initializeVariables() {
this.absoluteValue = 0;
this.formattedValue = '';

Loading…
Cancel
Save