Skip to content

Commit

Permalink
Added Trim and CopyButton components
Browse files Browse the repository at this point in the history
  • Loading branch information
razvantomegea committed Jan 30, 2025
1 parent 2c36c4c commit 03e8d06
Show file tree
Hide file tree
Showing 30 changed files with 546 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added Trim and CopyButton components](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/28)
- [Added transaction account](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/27)
- [Clean package.json export](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/26)
- [Updated transactions table props](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/25)
Expand Down
62 changes: 51 additions & 11 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { CustomToastType, IComponentToast, ISimpleToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type";
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { CustomToastType, IComponentToast, ISimpleToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type";
import { LocalJSX as JSX, VNode } from "@stencil/core";
import { ILedgerConnectModalData } from "./components/ledger-connect-modal/ledger-connect-modal.types";
import { IEventBus } from "./utils/EventBus";
Expand All @@ -15,8 +15,8 @@ import { ISignTransactionsModalData } from "./components/sign-transactions-modal
import { CustomToastType as CustomToastType1, IToastDataState, ITransaction, ITransactionProgressState, ITransactionToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type";
import { ITransactionAccount, ITransactionIconInfo, ITransactionsTableRow } from "./components/transactions-table/transactions-table.type";
import { IWalletConnectModalData } from "./components/wallet-connect-modal/wallet-connect-modal.types";
export { CustomToastType, IComponentToast, ISimpleToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type";
export { IconDefinition } from "@fortawesome/free-solid-svg-icons";
export { CustomToastType, IComponentToast, ISimpleToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type";
export { LocalJSX as JSX, VNode } from "@stencil/core";
export { ILedgerConnectModalData } from "./components/ledger-connect-modal/ledger-connect-modal.types";
export { IEventBus } from "./utils/EventBus";
Expand All @@ -32,6 +32,12 @@ export namespace Components {
"ticker": string;
"usdValue"?: string;
}
interface CopyButton {
"class"?: string;
"copyIcon": IconDefinition;
"successIcon": IconDefinition;
"text": string;
}
interface CustomToast {
"toast": IComponentToast;
}
Expand All @@ -42,7 +48,7 @@ export namespace Components {
"link": string;
"text"?: string;
}
interface FontawesomeIcon {
interface FaIcon {
"class"?: string;
"description"?: string;
"icon": IconDefinition;
Expand Down Expand Up @@ -177,6 +183,11 @@ export namespace Components {
"class"?: string;
"data": string;
}
interface TrimText {
"class"?: string;
"dataTestId": string;
"text": string;
}
interface WalletConnectModal {
"data": IWalletConnectModalData;
"getEventBus": () => Promise<IEventBus>;
Expand Down Expand Up @@ -213,6 +224,12 @@ declare global {
prototype: HTMLBalanceComponentElement;
new (): HTMLBalanceComponentElement;
};
interface HTMLCopyButtonElement extends Components.CopyButton, HTMLStencilElement {
}
var HTMLCopyButtonElement: {
prototype: HTMLCopyButtonElement;
new (): HTMLCopyButtonElement;
};
interface HTMLCustomToastElementEventMap {
"handleDeleteToast": string;
}
Expand All @@ -236,11 +253,11 @@ declare global {
prototype: HTMLExplorerLinkElement;
new (): HTMLExplorerLinkElement;
};
interface HTMLFontawesomeIconElement extends Components.FontawesomeIcon, HTMLStencilElement {
interface HTMLFaIconElement extends Components.FaIcon, HTMLStencilElement {
}
var HTMLFontawesomeIconElement: {
prototype: HTMLFontawesomeIconElement;
new (): HTMLFontawesomeIconElement;
var HTMLFaIconElement: {
prototype: HTMLFaIconElement;
new (): HTMLFaIconElement;
};
interface HTMLFormatAmountElement extends Components.FormatAmount, HTMLStencilElement {
}
Expand Down Expand Up @@ -459,6 +476,12 @@ declare global {
prototype: HTMLTransactionsTableElement;
new (): HTMLTransactionsTableElement;
};
interface HTMLTrimTextElement extends Components.TrimText, HTMLStencilElement {
}
var HTMLTrimTextElement: {
prototype: HTMLTrimTextElement;
new (): HTMLTrimTextElement;
};
interface HTMLWalletConnectModalElement extends Components.WalletConnectModal, HTMLStencilElement {
}
var HTMLWalletConnectModalElement: {
Expand All @@ -467,9 +490,10 @@ declare global {
};
interface HTMLElementTagNameMap {
"balance-component": HTMLBalanceComponentElement;
"copy-button": HTMLCopyButtonElement;
"custom-toast": HTMLCustomToastElement;
"explorer-link": HTMLExplorerLinkElement;
"fontawesome-icon": HTMLFontawesomeIconElement;
"fa-icon": HTMLFaIconElement;
"format-amount": HTMLFormatAmountElement;
"fungible-component": HTMLFungibleComponentElement;
"generic-modal": HTMLGenericModalElement;
Expand Down Expand Up @@ -497,6 +521,7 @@ declare global {
"transaction-toast-progress": HTMLTransactionToastProgressElement;
"transaction-toast-wrapper": HTMLTransactionToastWrapperElement;
"transactions-table": HTMLTransactionsTableElement;
"trim-text": HTMLTrimTextElement;
"wallet-connect-modal": HTMLWalletConnectModalElement;
}
}
Expand All @@ -507,6 +532,12 @@ declare namespace LocalJSX {
"ticker"?: string;
"usdValue"?: string;
}
interface CopyButton {
"class"?: string;
"copyIcon"?: IconDefinition;
"successIcon"?: IconDefinition;
"text"?: string;
}
interface CustomToast {
"onHandleDeleteToast"?: (event: CustomToastCustomEvent<string>) => void;
"toast"?: IComponentToast;
Expand All @@ -518,7 +549,7 @@ declare namespace LocalJSX {
"link"?: string;
"text"?: string;
}
interface FontawesomeIcon {
interface FaIcon {
"class"?: string;
"description"?: string;
"icon"?: IconDefinition;
Expand Down Expand Up @@ -654,14 +685,20 @@ declare namespace LocalJSX {
"class"?: string;
"data"?: string;
}
interface TrimText {
"class"?: string;
"dataTestId"?: string;
"text"?: string;
}
interface WalletConnectModal {
"data"?: IWalletConnectModalData;
}
interface IntrinsicElements {
"balance-component": BalanceComponent;
"copy-button": CopyButton;
"custom-toast": CustomToast;
"explorer-link": ExplorerLink;
"fontawesome-icon": FontawesomeIcon;
"fa-icon": FaIcon;
"format-amount": FormatAmount;
"fungible-component": FungibleComponent;
"generic-modal": GenericModal;
Expand Down Expand Up @@ -689,6 +726,7 @@ declare namespace LocalJSX {
"transaction-toast-progress": TransactionToastProgress;
"transaction-toast-wrapper": TransactionToastWrapper;
"transactions-table": TransactionsTable;
"trim-text": TrimText;
"wallet-connect-modal": WalletConnectModal;
}
}
Expand All @@ -697,9 +735,10 @@ declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"balance-component": LocalJSX.BalanceComponent & JSXBase.HTMLAttributes<HTMLBalanceComponentElement>;
"copy-button": LocalJSX.CopyButton & JSXBase.HTMLAttributes<HTMLCopyButtonElement>;
"custom-toast": LocalJSX.CustomToast & JSXBase.HTMLAttributes<HTMLCustomToastElement>;
"explorer-link": LocalJSX.ExplorerLink & JSXBase.HTMLAttributes<HTMLExplorerLinkElement>;
"fontawesome-icon": LocalJSX.FontawesomeIcon & JSXBase.HTMLAttributes<HTMLFontawesomeIconElement>;
"fa-icon": LocalJSX.FaIcon & JSXBase.HTMLAttributes<HTMLFaIconElement>;
"format-amount": LocalJSX.FormatAmount & JSXBase.HTMLAttributes<HTMLFormatAmountElement>;
"fungible-component": LocalJSX.FungibleComponent & JSXBase.HTMLAttributes<HTMLFungibleComponentElement>;
"generic-modal": LocalJSX.GenericModal & JSXBase.HTMLAttributes<HTMLGenericModalElement>;
Expand Down Expand Up @@ -727,6 +766,7 @@ declare module "@stencil/core" {
"transaction-toast-progress": LocalJSX.TransactionToastProgress & JSXBase.HTMLAttributes<HTMLTransactionToastProgressElement>;
"transaction-toast-wrapper": LocalJSX.TransactionToastWrapper & JSXBase.HTMLAttributes<HTMLTransactionToastWrapperElement>;
"transactions-table": LocalJSX.TransactionsTable & JSXBase.HTMLAttributes<HTMLTransactionsTableElement>;
"trim-text": LocalJSX.TrimText & JSXBase.HTMLAttributes<HTMLTrimTextElement>;
"wallet-connect-modal": LocalJSX.WalletConnectModal & JSXBase.HTMLAttributes<HTMLWalletConnectModalElement>;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/copy-button/copy-button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.copy-button {
color: #6c757d;
}
51 changes: 51 additions & 0 deletions src/components/copy-button/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Component, Prop, h, State } from '@stencil/core';
import { faCheck, faCopy, IconDefinition } from '@fortawesome/free-solid-svg-icons';
import { copyToClipboard } from 'utils/copyToClipboard';

@Component({
tag: 'copy-button',
styleUrl: 'copy-button.css',
shadow: true,
})
export class CopyButton {
@Prop() class?: string = 'copy-button';
@Prop() copyIcon: IconDefinition = faCopy;
@Prop() successIcon: IconDefinition = faCheck;
@Prop() text: string;

@State() isSuccess: boolean = false;

private timeoutId: number | undefined;

private handleClick = async (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();

const trimmedText = this.text ? this.text.trim() : this.text;
const success = await copyToClipboard(trimmedText);

this.isSuccess = success;

if (success) {
this.timeoutId = window.setTimeout(() => {
this.isSuccess = false;
}, 1000);
}
};

disconnectedCallback() {
// Clear the timeout if the component is unmounted
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = undefined; // Reset the timeout ID
}
}

render() {
return (
<a href="/#" class={this.class} onClick={this.handleClick}>
<fa-icon icon={this.isSuccess ? this.successIcon : this.copyIcon}></fa-icon>
</a>
);
}
}
109 changes: 109 additions & 0 deletions src/components/copy-button/tests/copy-button.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { newSpecPage } from '@stencil/core/testing';
import { CopyButton } from '../copy-button';
import * as copyUtils from 'utils/copyToClipboard';

describe('CopyButton', () => {
it('renders with default props', async () => {
const page = await newSpecPage({
components: [CopyButton],
html: '<copy-button text="Copy me"></copy-button>',
});

expect(page.root).toEqualHtml(`
<copy-button text="Copy me">
<mock:shadow-root>
<a href="/#" class="copy-button">
<fa-icon></fa-icon>
</a>
</mock:shadow-root>
</copy-button>
`);
});

it('renders with custom class', async () => {
const page = await newSpecPage({
components: [CopyButton],
html: '<copy-button class="custom-class" text="Copy me"></copy-button>',
});

expect(page.root).toEqualHtml(`
<copy-button class="custom-class" text="Copy me">
<mock:shadow-root>
<a href="/#" class="custom-class">
<fa-icon></fa-icon>
</a>
</mock:shadow-root>
</copy-button>
`);
});

it('changes to success icon when clicked and copy succeeds', async () => {
jest.spyOn(copyUtils, 'copyToClipboard').mockResolvedValue(true);

const page = await newSpecPage({
components: [CopyButton],
html: '<copy-button text="Copy me"></copy-button>',
});

const copyButton = page.root;
const anchor = copyButton.shadowRoot.querySelector('a');

await anchor.click();
await page.waitForChanges();

expect(copyButton).toEqualHtml(`
<copy-button text="Copy me">
<mock:shadow-root>
<a href="/#" class="copy-button">
<fa-icon></fa-icon>
</a>
</mock:shadow-root>
</copy-button>
`);
});

it('remains with copy icon when clicked and copy fails', async () => {
jest.spyOn(copyUtils, 'copyToClipboard').mockResolvedValue(false);

const page = await newSpecPage({
components: [CopyButton],
html: '<copy-button text="Copy me"></copy-button>',
});

const copyButton = page.root;
const anchor = copyButton.shadowRoot.querySelector('a');

await anchor.click();
await page.waitForChanges();

expect(copyButton).toEqualHtml(`
<copy-button text="Copy me">
<mock:shadow-root>
<a href="/#" class="copy-button">
<fa-icon></fa-icon>
</a>
</mock:shadow-root>
</copy-button>
`);
});

it('prevents default behavior and stops propagation on click', async () => {
const page = await newSpecPage({
components: [CopyButton],
html: '<copy-button text="Copy me"></copy-button>',
});

const copyButton = page.root;
const anchor = copyButton.shadowRoot.querySelector('a');

const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
};

anchor.dispatchEvent(new MouseEvent('click', mockEvent as any));

expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
expect(mockEvent.stopPropagation).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { getIconHtmlFromIconDefinition } from 'utils/icons/getIconHtmlFromIconDe
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';

@Component({
tag: 'fontawesome-icon',
styleUrl: 'fontawesome-icon.css',
tag: 'fa-icon',
shadow: true,
})
export class FontawesomeIcon {
@Prop() class?: string = 'fontawesome-icon';
export class FaIcon {
@Prop() class?: string = 'fa-icon';
@Prop() icon: IconDefinition;
@Prop() description?: string;

Expand Down
Loading

0 comments on commit 03e8d06

Please sign in to comment.