From fda0c3afc843690a50868c0a545000bcfaf214dc Mon Sep 17 00:00:00 2001
From: Erwin <111194281+Erwin-N@users.noreply.github.com>
Date: Mon, 30 Mar 2026 20:31:06 +0200
Subject: [PATCH] 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>
---
CHANGELOG.md | 4 ++
apps/client/src/styles.scss | 4 ++
libs/ui/src/lib/value/value.component.html | 30 +++++++++-
.../src/lib/value/value.component.stories.ts | 6 +-
libs/ui/src/lib/value/value.component.ts | 58 +++++++++++++++++--
5 files changed, 95 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eedf1b79c..6721e3022 100644
--- a/CHANGELOG.md
+++ b/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`)
diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss
index 142d2ea53..f5a4e9c80 100644
--- a/apps/client/src/styles.scss
+++ b/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;
}
diff --git a/libs/ui/src/lib/value/value.component.html b/libs/ui/src/lib/value/value.component.html
index 14080c16d..c4d6532a7 100644
--- a/libs/ui/src/lib/value/value.component.html
+++ b/libs/ui/src/lib/value/value.component.html
@@ -4,7 +4,26 @@
}
-
+
+
+
+
+
+ @if (enableCopyToClipboardButton) {
+
+ }
+
+
@if (value || value === 0 || value === null) {
}
+ @if (!hasLabel) {
+
+ }
}
@@ -88,6 +110,9 @@
@if (size === 'large') {
+ @if (hasLabel) {
+
+ }
@if (subLabel) {
{{ subLabel }}
}
@@ -95,6 +120,9 @@
} @else {
+ @if (hasLabel) {
+
+ }
}
diff --git a/libs/ui/src/lib/value/value.component.stories.ts b/libs/ui/src/lib/value/value.component.stories.ts
index eeebe9399..214331efc 100644
--- a/libs/ui/src/lib/value/value.component.stories.ts
+++ b/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: `
Label`
+ template: `
Label`
})
};
diff --git a/libs/ui/src/lib/value/value.component.ts b/libs/ui/src/lib/value/value.component.ts
index 795e16491..9c3330466 100644
--- a/libs/ui/src/lib/value/value.component.ts
+++ b/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
;
+
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();
private readonly formatOptions = computed(() => {
@@ -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 = '';