Browse Source

Task/improve copy-to-clipboard feedback in value component (#7338)

* Improve copy-to-clipboard feedback

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/7366/head
Chanhyo Jung 3 days ago
committed by GitHub
parent
commit
74914d973c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 9
      libs/ui/src/lib/value/value.component.html
  3. 35
      libs/ui/src/lib/value/value.component.ts

1
CHANGELOG.md

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the copy-to-clipboard functionality in the value component by providing a visual confirmation
- Improved the language localization for German (`de`) - Improved the language localization for German (`de`)
- Upgraded `stripe` from version `22.2.3` to `22.3.2` - Upgraded `stripe` from version `22.2.3` to `22.3.2`

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

@ -13,13 +13,16 @@
@if (enableCopyToClipboardButton) { @if (enableCopyToClipboardButton) {
<button <button
class="ml-1 no-height no-min-width p-1" class="ml-1 no-height no-min-width p-1"
i18n-title
mat-button mat-button
title="Copy to clipboard"
type="button" type="button"
[title]="isCopied ? copiedTitle : copyToClipboardTitle"
(click)="onCopyValueToClipboard()" (click)="onCopyValueToClipboard()"
(mousedown)="$event.preventDefault()"
> >
<ion-icon class="text-muted" name="copy-outline" /> <ion-icon
class="text-muted"
[name]="isCopied ? 'checkmark-outline' : 'copy-outline'"
/>
</button> </button>
} }
</ng-template> </ng-template>

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

@ -13,13 +13,14 @@ import {
input, input,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { copyOutline } from 'ionicons/icons'; import { checkmarkOutline, copyOutline } from 'ionicons/icons';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import ms from 'ms'; import ms from 'ms';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -32,7 +33,7 @@ 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 AfterViewInit, OnChanges { export class GfValueComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() colorizeSign = false; @Input() colorizeSign = false;
@Input() deviceType: string; @Input() deviceType: string;
@Input() enableCopyToClipboardButton = false; @Input() enableCopyToClipboardButton = false;
@ -54,22 +55,29 @@ export class GfValueComponent implements AfterViewInit, OnChanges {
public absoluteValue = 0; public absoluteValue = 0;
public formattedValue = ''; public formattedValue = '';
public hasLabel = false; public hasLabel = false;
public isCopied = false;
public isNumber = false; public isNumber = false;
public isString = false; public isString = false;
public useAbsoluteValue = false; public useAbsoluteValue = false;
public readonly copiedTitle = $localize`The value has been copied to the clipboard`;
public readonly copyToClipboardTitle = $localize`Copy to clipboard`;
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private clipboard: Clipboard, private clipboard: Clipboard,
private snackBar: MatSnackBar private snackBar: MatSnackBar
) { ) {
addIcons({ addIcons({
checkmarkOutline,
copyOutline copyOutline
}); });
} }
public readonly precision = input<number>(); public readonly precision = input<number>();
private copyToClipboardTimeout: ReturnType<typeof setTimeout>;
private readonly formatOptions = computed<Intl.NumberFormatOptions>(() => { private readonly formatOptions = computed<Intl.NumberFormatOptions>(() => {
const digits = this.hasPrecision ? this.precision() : 2; const digits = this.hasPrecision ? this.precision() : 2;
@ -178,6 +186,18 @@ export class GfValueComponent implements AfterViewInit, OnChanges {
public onCopyValueToClipboard() { public onCopyValueToClipboard() {
this.clipboard.copy(String(this.value)); this.clipboard.copy(String(this.value));
this.isCopied = true;
if (this.copyToClipboardTimeout) {
clearTimeout(this.copyToClipboardTimeout);
}
this.copyToClipboardTimeout = setTimeout(() => {
this.isCopied = false;
this.changeDetectorRef.markForCheck();
}, ms('3 seconds'));
this.snackBar.open( this.snackBar.open(
'✅ ' + $localize`${this.value} has been copied to the clipboard`, '✅ ' + $localize`${this.value} has been copied to the clipboard`,
undefined, undefined,
@ -187,12 +207,23 @@ export class GfValueComponent implements AfterViewInit, OnChanges {
); );
} }
public ngOnDestroy() {
if (this.copyToClipboardTimeout) {
clearTimeout(this.copyToClipboardTimeout);
}
}
private initializeVariables() { private initializeVariables() {
this.absoluteValue = 0; this.absoluteValue = 0;
this.formattedValue = ''; this.formattedValue = '';
this.isCopied = false;
this.isNumber = false; this.isNumber = false;
this.isString = false; this.isString = false;
this.locale = this.locale || getLocale(); this.locale = this.locale || getLocale();
this.useAbsoluteValue = false; this.useAbsoluteValue = false;
if (this.copyToClipboardTimeout) {
clearTimeout(this.copyToClipboardTimeout);
}
} }
} }

Loading…
Cancel
Save