diff --git a/CHANGELOG.md b/CHANGELOG.md index 45111036..4b47a6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [Added transactions table component](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/22) - [Fixed generic modal event warning](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/23) - [Added custom notification support](https://github.com/multiversx/mx-sdk-dapp-core-ui/pull/21) diff --git a/src/components.d.ts b/src/components.d.ts index 48449f2a..6b543661 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -6,20 +6,24 @@ */ 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 { LocalJSX as JSX, VNode } from "@stencil/core"; import { ILedgerConnectModalData } from "./components/ledger-connect-modal/ledger-connect-modal.types"; import { IEventBus } from "./utils/EventBus"; import { IPendingTransactionsModalData } from "./components/pending-transactions-modal/pending-transactions-modal.types"; import { ISignTransactionsModalData } from "./components/sign-transactions-modal/sign-transactions-modal.types"; import { CustomToastType as CustomToastType1, IToastDataState, ITransaction, ITransactionProgressState, ITransactionToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type"; +import { 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 { LocalJSX as JSX, VNode } from "@stencil/core"; export { ILedgerConnectModalData } from "./components/ledger-connect-modal/ledger-connect-modal.types"; export { IEventBus } from "./utils/EventBus"; export { IPendingTransactionsModalData } from "./components/pending-transactions-modal/pending-transactions-modal.types"; export { ISignTransactionsModalData } from "./components/sign-transactions-modal/sign-transactions-modal.types"; export { CustomToastType as CustomToastType1, IToastDataState, ITransaction, ITransactionProgressState, ITransactionToast } from "./components/toasts-list/components/transaction-toast/transaction-toast.type"; +export { ITransactionIconInfo, ITransactionsTableRow } from "./components/transactions-table/transactions-table.type"; export { IWalletConnectModalData } from "./components/wallet-connect-modal/wallet-connect-modal.types"; export namespace Components { interface BalanceComponent { @@ -31,6 +35,13 @@ export namespace Components { interface CustomToast { "toast": IComponentToast; } + interface ExplorerLink { + "class": string; + "dataTestId"?: string; + "icon"?: IconDefinition; + "link": string; + "text"?: string; + } interface FormatAmount { "class"?: string; "dataTestId"?: string; @@ -91,6 +102,23 @@ export namespace Components { } interface TokenComponent { } + interface TransactionAge { + "age": string; + "tooltip"?: string; + } + interface TransactionHash { + "transaction": ITransactionsTableRow; + } + interface TransactionIcon { + "iconInfo": ITransactionIconInfo; + } + interface TransactionMethod { + "transactionActionDescription": string; + "transactionMethod": string; + } + interface TransactionRow { + "transaction": ITransactionsTableRow; + } interface TransactionToast { "processedTransactionsStatus": string | JSX.Element; "toastDataState": IToastDataState; @@ -122,6 +150,9 @@ export namespace Components { "wrapperClass": string; "wrapperId"?: string; } + interface TransactionsTable { + "transactions": ITransactionsTableRow[]; + } interface WalletConnectModal { "data": IWalletConnectModalData; "getEventBus": () => Promise; @@ -175,6 +206,12 @@ declare global { prototype: HTMLCustomToastElement; new (): HTMLCustomToastElement; }; + interface HTMLExplorerLinkElement extends Components.ExplorerLink, HTMLStencilElement { + } + var HTMLExplorerLinkElement: { + prototype: HTMLExplorerLinkElement; + new (): HTMLExplorerLinkElement; + }; interface HTMLFormatAmountElement extends Components.FormatAmount, HTMLStencilElement { } var HTMLFormatAmountElement: { @@ -286,6 +323,36 @@ declare global { prototype: HTMLTokenComponentElement; new (): HTMLTokenComponentElement; }; + interface HTMLTransactionAgeElement extends Components.TransactionAge, HTMLStencilElement { + } + var HTMLTransactionAgeElement: { + prototype: HTMLTransactionAgeElement; + new (): HTMLTransactionAgeElement; + }; + interface HTMLTransactionHashElement extends Components.TransactionHash, HTMLStencilElement { + } + var HTMLTransactionHashElement: { + prototype: HTMLTransactionHashElement; + new (): HTMLTransactionHashElement; + }; + interface HTMLTransactionIconElement extends Components.TransactionIcon, HTMLStencilElement { + } + var HTMLTransactionIconElement: { + prototype: HTMLTransactionIconElement; + new (): HTMLTransactionIconElement; + }; + interface HTMLTransactionMethodElement extends Components.TransactionMethod, HTMLStencilElement { + } + var HTMLTransactionMethodElement: { + prototype: HTMLTransactionMethodElement; + new (): HTMLTransactionMethodElement; + }; + interface HTMLTransactionRowElement extends Components.TransactionRow, HTMLStencilElement { + } + var HTMLTransactionRowElement: { + prototype: HTMLTransactionRowElement; + new (): HTMLTransactionRowElement; + }; interface HTMLTransactionToastElementEventMap { "handleDeleteToast": string; } @@ -344,6 +411,12 @@ declare global { prototype: HTMLTransactionToastWrapperElement; new (): HTMLTransactionToastWrapperElement; }; + interface HTMLTransactionsTableElement extends Components.TransactionsTable, HTMLStencilElement { + } + var HTMLTransactionsTableElement: { + prototype: HTMLTransactionsTableElement; + new (): HTMLTransactionsTableElement; + }; interface HTMLWalletConnectModalElement extends Components.WalletConnectModal, HTMLStencilElement { } var HTMLWalletConnectModalElement: { @@ -353,6 +426,7 @@ declare global { interface HTMLElementTagNameMap { "balance-component": HTMLBalanceComponentElement; "custom-toast": HTMLCustomToastElement; + "explorer-link": HTMLExplorerLinkElement; "format-amount": HTMLFormatAmountElement; "fungible-component": HTMLFungibleComponentElement; "generic-modal": HTMLGenericModalElement; @@ -366,12 +440,18 @@ declare global { "simple-toast": HTMLSimpleToastElement; "toast-list": HTMLToastListElement; "token-component": HTMLTokenComponentElement; + "transaction-age": HTMLTransactionAgeElement; + "transaction-hash": HTMLTransactionHashElement; + "transaction-icon": HTMLTransactionIconElement; + "transaction-method": HTMLTransactionMethodElement; + "transaction-row": HTMLTransactionRowElement; "transaction-toast": HTMLTransactionToastElement; "transaction-toast-content": HTMLTransactionToastContentElement; "transaction-toast-details": HTMLTransactionToastDetailsElement; "transaction-toast-details-body": HTMLTransactionToastDetailsBodyElement; "transaction-toast-progress": HTMLTransactionToastProgressElement; "transaction-toast-wrapper": HTMLTransactionToastWrapperElement; + "transactions-table": HTMLTransactionsTableElement; "wallet-connect-modal": HTMLWalletConnectModalElement; } } @@ -386,6 +466,13 @@ declare namespace LocalJSX { "onHandleDeleteToast"?: (event: CustomToastCustomEvent) => void; "toast"?: IComponentToast; } + interface ExplorerLink { + "class"?: string; + "dataTestId"?: string; + "icon"?: IconDefinition; + "link"?: string; + "text"?: string; + } interface FormatAmount { "class"?: string; "dataTestId"?: string; @@ -445,6 +532,23 @@ declare namespace LocalJSX { } interface TokenComponent { } + interface TransactionAge { + "age"?: string; + "tooltip"?: string; + } + interface TransactionHash { + "transaction"?: ITransactionsTableRow; + } + interface TransactionIcon { + "iconInfo"?: ITransactionIconInfo; + } + interface TransactionMethod { + "transactionActionDescription"?: string; + "transactionMethod"?: string; + } + interface TransactionRow { + "transaction"?: ITransactionsTableRow; + } interface TransactionToast { "onHandleDeleteToast"?: (event: TransactionToastCustomEvent) => void; "processedTransactionsStatus"?: string | JSX.Element; @@ -478,12 +582,16 @@ declare namespace LocalJSX { "wrapperClass"?: string; "wrapperId"?: string; } + interface TransactionsTable { + "transactions"?: ITransactionsTableRow[]; + } interface WalletConnectModal { "data"?: IWalletConnectModalData; } interface IntrinsicElements { "balance-component": BalanceComponent; "custom-toast": CustomToast; + "explorer-link": ExplorerLink; "format-amount": FormatAmount; "fungible-component": FungibleComponent; "generic-modal": GenericModal; @@ -497,12 +605,18 @@ declare namespace LocalJSX { "simple-toast": SimpleToast; "toast-list": ToastList; "token-component": TokenComponent; + "transaction-age": TransactionAge; + "transaction-hash": TransactionHash; + "transaction-icon": TransactionIcon; + "transaction-method": TransactionMethod; + "transaction-row": TransactionRow; "transaction-toast": TransactionToast; "transaction-toast-content": TransactionToastContent; "transaction-toast-details": TransactionToastDetails; "transaction-toast-details-body": TransactionToastDetailsBody; "transaction-toast-progress": TransactionToastProgress; "transaction-toast-wrapper": TransactionToastWrapper; + "transactions-table": TransactionsTable; "wallet-connect-modal": WalletConnectModal; } } @@ -512,6 +626,7 @@ declare module "@stencil/core" { interface IntrinsicElements { "balance-component": LocalJSX.BalanceComponent & JSXBase.HTMLAttributes; "custom-toast": LocalJSX.CustomToast & JSXBase.HTMLAttributes; + "explorer-link": LocalJSX.ExplorerLink & JSXBase.HTMLAttributes; "format-amount": LocalJSX.FormatAmount & JSXBase.HTMLAttributes; "fungible-component": LocalJSX.FungibleComponent & JSXBase.HTMLAttributes; "generic-modal": LocalJSX.GenericModal & JSXBase.HTMLAttributes; @@ -525,12 +640,18 @@ declare module "@stencil/core" { "simple-toast": LocalJSX.SimpleToast & JSXBase.HTMLAttributes; "toast-list": LocalJSX.ToastList & JSXBase.HTMLAttributes; "token-component": LocalJSX.TokenComponent & JSXBase.HTMLAttributes; + "transaction-age": LocalJSX.TransactionAge & JSXBase.HTMLAttributes; + "transaction-hash": LocalJSX.TransactionHash & JSXBase.HTMLAttributes; + "transaction-icon": LocalJSX.TransactionIcon & JSXBase.HTMLAttributes; + "transaction-method": LocalJSX.TransactionMethod & JSXBase.HTMLAttributes; + "transaction-row": LocalJSX.TransactionRow & JSXBase.HTMLAttributes; "transaction-toast": LocalJSX.TransactionToast & JSXBase.HTMLAttributes; "transaction-toast-content": LocalJSX.TransactionToastContent & JSXBase.HTMLAttributes; "transaction-toast-details": LocalJSX.TransactionToastDetails & JSXBase.HTMLAttributes; "transaction-toast-details-body": LocalJSX.TransactionToastDetailsBody & JSXBase.HTMLAttributes; "transaction-toast-progress": LocalJSX.TransactionToastProgress & JSXBase.HTMLAttributes; "transaction-toast-wrapper": LocalJSX.TransactionToastWrapper & JSXBase.HTMLAttributes; + "transactions-table": LocalJSX.TransactionsTable & JSXBase.HTMLAttributes; "wallet-connect-modal": LocalJSX.WalletConnectModal & JSXBase.HTMLAttributes; } } diff --git a/src/components/explorer-link/explorer-link.css b/src/components/explorer-link/explorer-link.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/explorer-link/explorer-link.tsx b/src/components/explorer-link/explorer-link.tsx new file mode 100644 index 00000000..c1c55a87 --- /dev/null +++ b/src/components/explorer-link/explorer-link.tsx @@ -0,0 +1,26 @@ +import { Component, Prop, h } from '@stencil/core'; +import { getIconHtmlFromIconDefinition } from 'utils/icons/getIconHtmlFromIconDefinition'; +import { IconDefinition, faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'; + +@Component({ + tag: 'explorer-link', + styleUrl: 'explorer-link.css', + shadow: true, +}) +export class ExplorerLink { + @Prop() class: string = 'explorer-link'; + @Prop() dataTestId?: string; + @Prop() icon?: IconDefinition; + @Prop() link: string; + @Prop() text?: string; + + render() { + const icon = getIconHtmlFromIconDefinition(this.icon ?? faArrowUpRightFromSquare); + + return ( + + {this.text ?? } + + ); + } +} diff --git a/src/components/explorer-link/readme.md b/src/components/explorer-link/readme.md new file mode 100644 index 00000000..74720702 --- /dev/null +++ b/src/components/explorer-link/readme.md @@ -0,0 +1,34 @@ +# explorer-link + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------ | -------------- | ----------- | ---------------- | ----------------- | +| `class` | `class` | | `string` | `'explorer-link'` | +| `dataTestId` | `data-test-id` | | `string` | `undefined` | +| `icon` | -- | | `IconDefinition` | `undefined` | +| `link` | `link` | | `string` | `undefined` | +| `text` | `text` | | `string` | `undefined` | + + +## Dependencies + +### Used by + + - [transaction-hash](../transactions-table/components/transaction-hash) + +### Graph +```mermaid +graph TD; + transaction-hash --> explorer-link + style explorer-link fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/explorer-link/tests/explorer-link.spec.ts b/src/components/explorer-link/tests/explorer-link.spec.ts new file mode 100644 index 00000000..33f62e91 --- /dev/null +++ b/src/components/explorer-link/tests/explorer-link.spec.ts @@ -0,0 +1,83 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { ExplorerLink } from '../explorer-link'; +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import * as iconUtils from 'utils/icons/getIconHtmlFromIconDefinition'; + +describe('ExplorerLink', () => { + it('renders with default props', async () => { + const page = await newSpecPage({ + components: [ExplorerLink], + html: '', + }); + expect(page.root).toEqualHtml(` + + + + + + + + + + `); + }); + + it('renders with custom text', async () => { + const page = await newSpecPage({ + components: [ExplorerLink], + html: '', + }); + expect(page.root).toEqualHtml(` + + + + View on Explorer + + + + `); + }); + + it('renders with custom class', async () => { + const page = await newSpecPage({ + components: [ExplorerLink], + html: '', + }); + expect(page.root).toEqualHtml(` + + + + + + + + + + `); + }); + + it('renders with custom icon', async () => { + const mockIconHtml = ''; + jest.spyOn(iconUtils, 'getIconHtmlFromIconDefinition').mockReturnValue(mockIconHtml); + + const page = await newSpecPage({ + components: [ExplorerLink], + html: '', + }); + + page.root.icon = faCheck; + await page.waitForChanges(); + + expect(page.root).toEqualHtml(` + + + + ${mockIconHtml} + + + + `); + + jest.restoreAllMocks(); + }); +}); diff --git a/src/components/format-amount/tests/format-amount.spec.ts b/src/components/format-amount/tests/format-amount.spec.ts index 49440fac..e2a68907 100644 --- a/src/components/format-amount/tests/format-amount.spec.ts +++ b/src/components/format-amount/tests/format-amount.spec.ts @@ -5,29 +5,25 @@ describe('FormatAmount component', () => { const renderComponent = async (props: any) => { const page = await newSpecPage({ components: [FormatAmount], - html: '', + html: ``, supportsShadowDom: true, }); - const component = page.root; - Object.keys(props).forEach((key) => { - component[key] = props[key]; - }); - await page.waitForChanges(); return page; }; - const decimalsSelector = (page) => { - return page.root.shadowRoot - .querySelector('span[data-testid="formatAmountDecimals"]') - ?.textContent; + const decimalsSelector = page => { + return page.root.shadowRoot.querySelector('span[data-testid="formatAmountDecimals"]')?.textContent; }; - const symbolSelector = (page) => { - return page.root.shadowRoot - .querySelectorAll('span[data-testid="formatAmountSymbol"]') - .length; + const symbolSelector = page => { + return page.root.shadowRoot.querySelectorAll('span[data-testid="formatAmountSymbol"]').length; }; it('should render valid amount with decimals', async () => { diff --git a/src/components/transactions-table/components/transaction-age/readme.md b/src/components/transactions-table/components/transaction-age/readme.md new file mode 100644 index 00000000..e2558b0d --- /dev/null +++ b/src/components/transactions-table/components/transaction-age/readme.md @@ -0,0 +1,18 @@ +# my-component + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------- | --------- | ----------- | -------- | ----------- | +| `age` | `age` | | `string` | `undefined` | +| `tooltip` | `tooltip` | | `string` | `undefined` | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/components/transaction-age/tests/transaction-age.spec.ts b/src/components/transactions-table/components/transaction-age/tests/transaction-age.spec.ts new file mode 100644 index 00000000..18dda4a3 --- /dev/null +++ b/src/components/transactions-table/components/transaction-age/tests/transaction-age.spec.ts @@ -0,0 +1,70 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { TransactionAge } from '../transaction-age'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +describe('TransactionAge', () => { + it('renders with age prop', async () => { + const page = await newSpecPage({ + components: [TransactionAge], + html: '', + }); + + const ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan).not.toBeNull(); + expect(ageSpan.textContent).toBe('2 days ago'); + }); + + it('renders without tooltip when not provided', async () => { + const page = await newSpecPage({ + components: [TransactionAge], + html: '', + }); + + const ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan).not.toBeNull(); + expect(ageSpan.getAttribute('title')).toBeNull(); + }); + + it('renders with tooltip when provided', async () => { + const page = await newSpecPage({ + components: [TransactionAge], + html: '', + }); + + const ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan).not.toBeNull(); + expect(ageSpan.getAttribute('title')).toBe('2023-05-17 10:30:00 UTC'); + }); + + it('updates when age prop changes', async () => { + const page = await newSpecPage({ + components: [TransactionAge], + html: '', + }); + + let ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan.textContent).toBe('5 minutes ago'); + + page.root.age = '10 minutes ago'; + await page.waitForChanges(); + + ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan.textContent).toBe('10 minutes ago'); + }); + + it('updates when tooltip prop changes', async () => { + const page = await newSpecPage({ + components: [TransactionAge], + html: '', + }); + + let ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan.getAttribute('title')).toBe('2023-05-17 09:30:00 UTC'); + + page.root.tooltip = '2023-05-17 10:30:00 UTC'; + await page.waitForChanges(); + + ageSpan = page.root.shadowRoot.querySelector(`[data-testid="${DataTestIdsEnum.transactionAge}"]`); + expect(ageSpan.getAttribute('title')).toBe('2023-05-17 10:30:00 UTC'); + }); +}); diff --git a/src/components/transactions-table/components/transaction-age/transaction-age.css b/src/components/transactions-table/components/transaction-age/transaction-age.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/transactions-table/components/transaction-age/transaction-age.tsx b/src/components/transactions-table/components/transaction-age/transaction-age.tsx new file mode 100644 index 00000000..0d1a5d53 --- /dev/null +++ b/src/components/transactions-table/components/transaction-age/transaction-age.tsx @@ -0,0 +1,24 @@ +import { Component, h, Prop } from '@stencil/core'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +@Component({ + tag: 'transaction-age', + styleUrl: 'transaction-age.css', + shadow: true, +}) +export class TransactionAge { + @Prop() age: string; + @Prop() tooltip?: string; + + render() { + const component = this.tooltip ? ( + + {this.age} + + ) : ( + {this.age} + ); + + return {component}; + } +} diff --git a/src/components/transactions-table/components/transaction-hash/readme.md b/src/components/transactions-table/components/transaction-hash/readme.md new file mode 100644 index 00000000..e0d29b11 --- /dev/null +++ b/src/components/transactions-table/components/transaction-hash/readme.md @@ -0,0 +1,37 @@ +# my-component + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------- | --------- | ----------- | ----------------------- | ----------- | +| `transaction` | -- | | `ITransactionsTableRow` | `undefined` | + + +## Dependencies + +### Used by + + - [transaction-row](../transaction-row) + +### Depends on + +- [transaction-icon](../transaction-icon) +- [explorer-link](../../../explorer-link) + +### Graph +```mermaid +graph TD; + transaction-hash --> transaction-icon + transaction-hash --> explorer-link + transaction-row --> transaction-hash + style transaction-hash fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/components/transaction-hash/tests/transaction-hash.spec.tsx b/src/components/transactions-table/components/transaction-hash/tests/transaction-hash.spec.tsx new file mode 100644 index 00000000..95f53d20 --- /dev/null +++ b/src/components/transactions-table/components/transaction-hash/tests/transaction-hash.spec.tsx @@ -0,0 +1,98 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { h } from '@stencil/core'; +import { TransactionHash } from '../transaction-hash'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; +import { ITransactionsTableRow } from '../../../transactions-table.type'; +import { faCircleInfo } from '@fortawesome/free-solid-svg-icons/faCircleInfo'; +import { faCircleCheck } from '@fortawesome/free-solid-svg-icons/faCircleCheck'; + +describe('TransactionHash', () => { + it('renders with transaction data', async () => { + const transaction: ITransactionsTableRow = { + age: { + timeAgo: '1h', + tooltip: '1 hour ago', + }, + iconInfo: { icon: faCircleInfo, tooltip: 'Test' }, + link: 'https://example.com/tx/123', + method: { + name: 'Smart Contract', + }, + txHash: '0x123456789abcdef', + }; + + const page = await newSpecPage({ + components: [TransactionHash], + template: () => , + }); + + expect(page.root).toEqualHtml(` + + +
+ + +
+
+
+ `); + }); + + it('updates when transaction prop changes', async () => { + const initialTransaction = { + age: { + timeAgo: '1h', + tooltip: '1 hour ago', + }, + iconInfo: { icon: faCircleInfo, tooltip: 'Initial' }, + link: 'https://example.com/tx/initial', + method: { + name: 'Smart Contract', + }, + txHash: '0xInitialHash', + }; + + const page = await newSpecPage({ + components: [TransactionHash], + template: () => , + }); + + expect(page.root).toEqualHtml(` + + +
+ + +
+
+
+ `); + + const updatedTransaction = { + age: { + timeAgo: '1h', + tooltip: '1 hour ago', + }, + iconInfo: { icon: faCircleCheck, tooltip: 'Updated' }, + link: 'https://example.com/tx/updated', + method: { + name: 'Smart Contract', + }, + txHash: '0xUpdatedHash', + }; + + page.root.transaction = updatedTransaction; + await page.waitForChanges(); + + expect(page.root).toEqualHtml(` + + +
+ + +
+
+
+ `); + }); +}); diff --git a/src/components/transactions-table/components/transaction-hash/transaction-hash.css b/src/components/transactions-table/components/transaction-hash/transaction-hash.css new file mode 100644 index 00000000..5d4e87f3 --- /dev/null +++ b/src/components/transactions-table/components/transaction-hash/transaction-hash.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/components/transactions-table/components/transaction-hash/transaction-hash.tsx b/src/components/transactions-table/components/transaction-hash/transaction-hash.tsx new file mode 100644 index 00000000..7591cfe7 --- /dev/null +++ b/src/components/transactions-table/components/transaction-hash/transaction-hash.tsx @@ -0,0 +1,21 @@ +import { Component, h, Prop } from '@stencil/core'; +import { ITransactionsTableRow } from '../../transactions-table.type'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +@Component({ + tag: 'transaction-hash', + styleUrl: 'transaction-hash.css', + shadow: true, +}) +export class TransactionHash { + @Prop() transaction: ITransactionsTableRow; + + render() { + return ( +
+ + +
+ ); + } +} diff --git a/src/components/transactions-table/components/transaction-icon/readme.md b/src/components/transactions-table/components/transaction-icon/readme.md new file mode 100644 index 00000000..2387af59 --- /dev/null +++ b/src/components/transactions-table/components/transaction-icon/readme.md @@ -0,0 +1,30 @@ +# my-component + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------- | --------- | ----------- | ---------------------- | ----------- | +| `iconInfo` | -- | | `ITransactionIconInfo` | `undefined` | + + +## Dependencies + +### Used by + + - [transaction-hash](../transaction-hash) + +### Graph +```mermaid +graph TD; + transaction-hash --> transaction-icon + style transaction-icon fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/components/transaction-icon/transaction-icon.css b/src/components/transactions-table/components/transaction-icon/transaction-icon.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/transactions-table/components/transaction-icon/transaction-icon.tsx b/src/components/transactions-table/components/transaction-icon/transaction-icon.tsx new file mode 100644 index 00000000..f1a7088d --- /dev/null +++ b/src/components/transactions-table/components/transaction-icon/transaction-icon.tsx @@ -0,0 +1,28 @@ +import { Component, Prop, h } from '@stencil/core'; +import { getIconHtmlFromIconDefinition } from 'utils/icons/getIconHtmlFromIconDefinition'; +import classNames from 'classnames'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { ITransactionIconInfo } from '../../transactions-table.type'; + +@Component({ + tag: 'transaction-icon', + styleUrl: 'transaction-icon.css', + shadow: true, +}) +export class TransactionIcon { + @Prop() iconInfo: ITransactionIconInfo; + + render() { + const iconHtml = getIconHtmlFromIconDefinition(this.iconInfo.icon); + + return ( +
+ ); + } +} diff --git a/src/components/transactions-table/components/transaction-method/readme.md b/src/components/transactions-table/components/transaction-method/readme.md new file mode 100644 index 00000000..970e2bfb --- /dev/null +++ b/src/components/transactions-table/components/transaction-method/readme.md @@ -0,0 +1,18 @@ +# my-component + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------------------------ | -------------------------------- | ----------- | -------- | ----------- | +| `transactionActionDescription` | `transaction-action-description` | | `string` | `undefined` | +| `transactionMethod` | `transaction-method` | | `string` | `undefined` | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/components/transaction-method/tests/transaction-method.spec.ts b/src/components/transactions-table/components/transaction-method/tests/transaction-method.spec.ts new file mode 100644 index 00000000..ffb2f41e --- /dev/null +++ b/src/components/transactions-table/components/transaction-method/tests/transaction-method.spec.ts @@ -0,0 +1,65 @@ +import { newE2EPage } from '@stencil/core/testing'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +describe('transaction-method', () => { + it('renders with default props', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('transaction-method'); + expect(element).not.toBeNull(); + expect(element.shadowRoot).not.toBeNull(); + }); + + it('displays the transaction method', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + await page.$eval('transaction-method', (elm: any) => { + elm.transactionMethod = 'testMethod'; + }); + await page.waitForChanges(); + + const methodElement = await page.find('transaction-method >>> .text-truncate'); + expect(methodElement.textContent).toBe('testMethod'); + }); + + it('sets the action description as title', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + await page.$eval('transaction-method', (elm: any) => { + elm.transactionActionDescription = 'Test Description'; + }); + await page.waitForChanges(); + + const spanElement = await page.find(`transaction-method >>> [data-testid="${DataTestIdsEnum.transactionMethod}"]`); + expect(await spanElement.getAttribute('title')).toBe('Test Description'); + }); + + it('applies correct CSS classes', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const outerSpan = await page.find('transaction-method >>> span'); + expect(outerSpan).toHaveClass('badge'); + expect(outerSpan).toHaveClass('badge-secondary'); + expect(outerSpan).toHaveClass('badge-pill'); + expect(outerSpan).toHaveClass('font-weight-light'); + expect(outerSpan).toHaveClass('p-0'); + + const innerDiv = await page.find('transaction-method >>> div > div'); + expect(innerDiv).toHaveClass('text-truncate'); + expect(innerDiv).toHaveClass('text-capitalize'); + expect(innerDiv).toHaveClass('text-white'); + expect(innerDiv).toHaveClass('p-1'); + }); + + it('has correct data-testid', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const spanElement = await page.find(`transaction-method >>> [data-testid="${DataTestIdsEnum.transactionMethod}"]`); + expect(spanElement).not.toBeNull(); + }); +}); diff --git a/src/components/transactions-table/components/transaction-method/transaction-method.css b/src/components/transactions-table/components/transaction-method/transaction-method.css new file mode 100644 index 00000000..d090b6fe --- /dev/null +++ b/src/components/transactions-table/components/transaction-method/transaction-method.css @@ -0,0 +1,177 @@ +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: pre-wrap; + word-break: break-all; + vertical-align: baseline; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .badge { + transition: none; + } +} + +a.badge:hover, a.badge:focus { + text-decoration: none; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #007bff; +} + +a.badge-primary:hover, a.badge-primary:focus { + color: #fff; + background-color: #0062cc; +} + +a.badge-primary:focus, a.badge-primary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.badge-secondary { + color: #fff; + background-color: #6c757d; +} + +a.badge-secondary:hover, a.badge-secondary:focus { + color: #fff; + background-color: #545b62; +} + +a.badge-secondary:focus, a.badge-secondary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.badge-success { + color: #fff; + background-color: #28a745; +} + +a.badge-success:hover, a.badge-success:focus { + color: #fff; + background-color: #1e7e34; +} + +a.badge-success:focus, a.badge-success.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.badge-info { + color: #fff; + background-color: #17a2b8; +} + +a.badge-info:hover, a.badge-info:focus { + color: #fff; + background-color: #117a8b; +} + +a.badge-info:focus, a.badge-info.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.badge-warning { + color: #212529; + background-color: #ffc107; +} + +a.badge-warning:hover, a.badge-warning:focus { + color: #212529; + background-color: #d39e00; +} + +a.badge-warning:focus, a.badge-warning.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.badge-danger { + color: #fff; + background-color: #dc3545; +} + +a.badge-danger:hover, a.badge-danger:focus { + color: #fff; + background-color: #bd2130; +} + +a.badge-danger:focus, a.badge-danger.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.badge-light { + color: #212529; + background-color: #f8f9fa; +} + +a.badge-light:hover, a.badge-light:focus { + color: #212529; + background-color: #dae0e5; +} + +a.badge-light:focus, a.badge-light.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} + +a.badge-dark:hover, a.badge-dark:focus { + color: #fff; + background-color: #1d2124; +} + +a.badge-dark:focus, a.badge-dark.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-capitalize { + text-transform: capitalize; +} + +.text-white { + color: #fff; +} + +.p-1 { + padding: 0.25rem; +} diff --git a/src/components/transactions-table/components/transaction-method/transaction-method.tsx b/src/components/transactions-table/components/transaction-method/transaction-method.tsx new file mode 100644 index 00000000..3b2bd6d5 --- /dev/null +++ b/src/components/transactions-table/components/transaction-method/transaction-method.tsx @@ -0,0 +1,24 @@ +import { Component, h, Prop } from '@stencil/core'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +@Component({ + tag: 'transaction-method', + styleUrl: 'transaction-method.css', + shadow: true, +}) +export class TransactionMethod { + @Prop() transactionActionDescription: string; + @Prop() transactionMethod: string; + + render() { + return ( +
+ +
+
{this.transactionMethod}
+
+
+
+ ); + } +} diff --git a/src/components/transactions-table/components/transaction-row/readme.md b/src/components/transactions-table/components/transaction-row/readme.md new file mode 100644 index 00000000..124bd6fd --- /dev/null +++ b/src/components/transactions-table/components/transaction-row/readme.md @@ -0,0 +1,37 @@ +# transaction-row + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------- | --------- | ----------- | ----------------------- | ----------- | +| `transaction` | -- | | `ITransactionsTableRow` | `undefined` | + + +## Dependencies + +### Used by + + - [transactions-table](../..) + +### Depends on + +- [transaction-hash](../transaction-hash) + +### Graph +```mermaid +graph TD; + transaction-row --> transaction-hash + transaction-hash --> transaction-icon + transaction-hash --> explorer-link + transactions-table --> transaction-row + style transaction-row fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/components/transaction-row/transaction-row.css b/src/components/transactions-table/components/transaction-row/transaction-row.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/transactions-table/components/transaction-row/transaction-row.tsx b/src/components/transactions-table/components/transaction-row/transaction-row.tsx new file mode 100644 index 00000000..875c69a4 --- /dev/null +++ b/src/components/transactions-table/components/transaction-row/transaction-row.tsx @@ -0,0 +1,21 @@ +import { Component, h, Prop } from '@stencil/core'; +import { ITransactionsTableRow } from '../../transactions-table.type'; + +@Component({ + tag: 'transaction-row', + styleUrl: 'transaction-row.css', + shadow: true, +}) +export class TransactionRow { + @Prop() transaction: ITransactionsTableRow; + + render() { + return ( + + + + + + ); + } +} diff --git a/src/components/transactions-table/readme.md b/src/components/transactions-table/readme.md new file mode 100644 index 00000000..0f7db50f --- /dev/null +++ b/src/components/transactions-table/readme.md @@ -0,0 +1,33 @@ +# transactions-table + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------- | --------- | ----------- | ------------------------- | ----------- | +| `transactions` | -- | | `ITransactionsTableRow[]` | `undefined` | + + +## Dependencies + +### Depends on + +- [transaction-row](./components/transaction-row) + +### Graph +```mermaid +graph TD; + transactions-table --> transaction-row + transaction-row --> transaction-hash + transaction-hash --> transaction-icon + transaction-hash --> explorer-link + style transactions-table fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/transactions-table/transactions-table.css b/src/components/transactions-table/transactions-table.css new file mode 100644 index 00000000..42da3e1d --- /dev/null +++ b/src/components/transactions-table/transactions-table.css @@ -0,0 +1,37 @@ +.transactions-table { + width: 100%; + border-top-width: 1px; + border-bottom-width: 1px; + border-color: #e5e7eb; + overflow: auto; + table-layout: auto; +} + +.transactions-table-header { + background-color: #f9fafb; +} + +.transactions-table-header-cell { + padding: 0.75rem 1rem; + text-align: left; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 500; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.05em; + width: 16.666667%; +} + +.transactions-table-body { + background-color: #ffffff; + border-top-width: 1px; + border-bottom-width: 1px; + border-color: #e5e7eb; +} + +.transactions-table-body-cell { + align-items: center; + display: flex; + justify-content: center; +} diff --git a/src/components/transactions-table/transactions-table.tsx b/src/components/transactions-table/transactions-table.tsx new file mode 100644 index 00000000..c4952186 --- /dev/null +++ b/src/components/transactions-table/transactions-table.tsx @@ -0,0 +1,34 @@ +import { Component, h, Prop } from '@stencil/core'; +import { ITransactionsTableRow } from './transactions-table.type'; + +const COLUMNS = ['TxHash', 'Age', 'Shard', 'From', 'To', 'Method', 'Value']; + +@Component({ + tag: 'transactions-table', + styleUrl: 'transactions-table.css', + shadow: true, +}) +export class TransactionsTable { + @Prop() transactions: ITransactionsTableRow[]; + + render() { + return ( + + + + {COLUMNS.map(column => ( + + ))} + + + + {this.transactions.map(transaction => ( + + ))} + +
+ {column} +
+ ); + } +} diff --git a/src/components/transactions-table/transactions-table.type.ts b/src/components/transactions-table/transactions-table.type.ts new file mode 100644 index 00000000..d6e10841 --- /dev/null +++ b/src/components/transactions-table/transactions-table.type.ts @@ -0,0 +1,24 @@ +import { IconDefinition } from '@fortawesome/free-solid-svg-icons'; + +export interface ITransactionsTableRow { + age: ITransactionAge; + method: ITransactionMethod; + iconInfo: ITransactionIconInfo; + link: string; + txHash: string; +} + +export interface ITransactionAge { + timeAgo: string; + tooltip: string; +} + +export interface ITransactionIconInfo { + icon?: IconDefinition; + tooltip: string; +} + +export interface ITransactionMethod { + name: string; + transactionActionDescription?: string; +} diff --git a/src/constants/dataTestIds.enum.ts b/src/constants/dataTestIds.enum.ts index 9ef6d7c0..995f5576 100644 --- a/src/constants/dataTestIds.enum.ts +++ b/src/constants/dataTestIds.enum.ts @@ -24,4 +24,7 @@ export enum DataTestIdsEnum { walletConnectQrCode = 'walletConnectQrCode', walletConnetModalSubtitle = 'walletConnetModalSubtitle', walletConnetModalTitle = 'walletConnetModalTitle', + transactionMethod = 'transactionMethod', + transactionAge = 'transactionAge', + transactionLink = 'transactionLink', }