diff --git a/src/common/Button/Button.tsx b/src/common/Button/Button.tsx new file mode 100644 index 00000000..506d2bb1 --- /dev/null +++ b/src/common/Button/Button.tsx @@ -0,0 +1,49 @@ +import { h } from '@stencil/core'; +import { ButtonSizeEnum, ButtonVariantEnum } from './button.types'; + +// prettier-ignore +const styles = { + button: 'button mvx:flex mvx:items-center mvx:justify-center mvx:font-bold mvx:leading-none mvx:px-4 mvx:max-h-full mvx:rounded-xl mvx:cursor-pointer mvx:transition-all mvx:duration-200 mvx:ease-in-out mvx:gap-2', + buttonLarge: 'button-large mvx:h-12 mvx:text-base mvx:px-6', + buttonSmall: 'button-small mvx:h-10 mvx:text-xs mvx:rounded-xl', + buttonPrimary: 'button-primary mvx:text-button-primary mvx:bg-button-bg-primary mvx:border mvx:border-button-bg-primary', + buttonSecondary: 'button-secondary mvx:relative mvx:text-button-secondary mvx:border mvx:border-transparent mvx:after:absolute mvx:after:inset-0 mvx:after:rounded-lg mvx:after:opacity-40 mvx:after:transition-all mvx:after:duration-200 mvx:after:ease-in-out mvx:after:bg-button-bg-secondary mvx:after:content-[""] mvx:after:-z-1 mvx:hover:opacity-100 mvx:hover:text-button-primary mvx:hover:after:opacity-100 mvx:hover:after:bg-button-bg-primary', + buttonSecondarySmall: 'button-secondary-small mvx:after:rounded-xl', + buttonNeutral: 'button-neutral mvx:text-neutral-925 mvx:bg-white mvx:hover:opacity-75', + buttonDisabled: 'button-disabled mvx:pointer-events-none mvx:bg-transparent mvx:cursor-default mvx:border mvx:border-secondary-text mvx:!text-secondary-text mvx:hover:opacity-100' +} satisfies Record; + +interface ButtonPropsType { + class?: string; + dataTestId?: string; + disabled?: boolean; + size?: `${ButtonSizeEnum}`; + variant?: `${ButtonVariantEnum}`; + onClick?: (event: MouseEvent) => void; +} + +export function Button({ class: className = '', dataTestId = '', disabled = false, size = 'large', variant = 'primary', onClick }: ButtonPropsType, children?: any) { + return ( + + ); + +} diff --git a/src/components/visual/button/button.types.ts b/src/common/Button/button.types.ts similarity index 100% rename from src/components/visual/button/button.types.ts rename to src/common/Button/button.types.ts diff --git a/src/common/CopyButton/CopyButton.tsx b/src/common/CopyButton/CopyButton.tsx new file mode 100644 index 00000000..9880c6ee --- /dev/null +++ b/src/common/CopyButton/CopyButton.tsx @@ -0,0 +1,38 @@ +import { h } from '@stencil/core'; +import { Icon } from 'common/Icon'; + +// prettier-ignore +const styles = { + copyButton: 'copy-button mvx:flex', + copyButtonIcon: 'copy-button-icon mvx:flex mvx:cursor-pointer mvx:justify-center mvx:transition-opacity mvx:duration-200 mvx:ease-in-out mvx:hover:opacity-80', + copyButtonIconCheck: 'copy-button-icon-check mvx:hover:opacity-100! mvx:cursor-default!', +} satisfies Record; + +interface CopyButtonPropsType { + iconClass?: string; + class?: string; + text: string; + isSuccessOnCopy?: boolean; + handleCopyButtonClick?: (event: MouseEvent) => void; +} + +export function CopyButton({ iconClass, class: className, isSuccessOnCopy, handleCopyButtonClick }: CopyButtonPropsType) { + return ( +
handleCopyButtonClick?.(event)} + class={{ + [styles.copyButton]: true, + [className]: Boolean(className), + }} + > + +
+ ); +} diff --git a/src/common/CopyButton/CopyButtonHandler.ts b/src/common/CopyButton/CopyButtonHandler.ts new file mode 100644 index 00000000..97f8a3bc --- /dev/null +++ b/src/common/CopyButton/CopyButtonHandler.ts @@ -0,0 +1,26 @@ +import { copyToClipboard } from 'utils/copyToClipboard'; + +interface CopyHandlerOptions { + onSuccessChange?: (isSuccess: boolean) => void; +} + +export function CopyButtonHandler({ onSuccessChange }: CopyHandlerOptions) { + let timeoutId: number | null = null; + + return async (event: MouseEvent, text?: string) => { + const trimmedText = text ? text.trim() : text; + const success = await copyToClipboard(trimmedText); + + event.preventDefault(); + event.stopPropagation(); + + if (onSuccessChange) { + onSuccessChange(success); + + window.clearTimeout(timeoutId); + if (success) { + timeoutId = window.setTimeout(() => onSuccessChange(false), 2000); + } + } + }; +} \ No newline at end of file diff --git a/src/common/Icon/Icon.tsx b/src/common/Icon/Icon.tsx index 2a431186..84ffa060 100644 --- a/src/common/Icon/Icon.tsx +++ b/src/common/Icon/Icon.tsx @@ -24,7 +24,9 @@ import { LayersIcon } from './components/LayersIcon'; import { LockIcon } from './components/LockIcon'; import { PencilIcon } from './components/PencilIcon'; import { TriangularWarningIcon } from './components/TriangularWarningIcon'; +import { SpinnerIcon } from './components/SpinnerIcon'; import type { IconPropsType } from './icon.types'; +import { ArrowRightIcon } from './components/ArrowRightIcon'; export const Icon = ({ name, ...properties }: IconPropsType) => { if (!name) { @@ -104,6 +106,12 @@ export const Icon = ({ name, ...properties }: IconPropsType) => { case 'arrows-rotate': return ; + case 'spinner': + return + + case 'arrow-right': + return + default: console.error(`No data for the ${name} icon.`); return null; diff --git a/src/common/Icon/components/ArrowRightIcon/ArrowRightIcon.tsx b/src/common/Icon/components/ArrowRightIcon/ArrowRightIcon.tsx new file mode 100644 index 00000000..c6a358be --- /dev/null +++ b/src/common/Icon/components/ArrowRightIcon/ArrowRightIcon.tsx @@ -0,0 +1,13 @@ +import { h } from '@stencil/core'; + +const styles = { + arrowRightIcon: 'arrow-right-icon mvx:w-4 mvx:h-4 mvx:text-inherit' +} satisfies Record; + +export const ArrowRightIcon = ({ class: className }: { class?: string }) => ( + + + +); + + diff --git a/src/common/Icon/components/ArrowRightIcon/index.ts b/src/common/Icon/components/ArrowRightIcon/index.ts new file mode 100644 index 00000000..190655e7 --- /dev/null +++ b/src/common/Icon/components/ArrowRightIcon/index.ts @@ -0,0 +1 @@ +export * from './ArrowRightIcon'; \ No newline at end of file diff --git a/src/common/Icon/components/SpinnerIcon/SpinnerIcon.tsx b/src/common/Icon/components/SpinnerIcon/SpinnerIcon.tsx new file mode 100644 index 00000000..0c6ee5ac --- /dev/null +++ b/src/common/Icon/components/SpinnerIcon/SpinnerIcon.tsx @@ -0,0 +1,16 @@ +import { h } from '@stencil/core'; + +const styles = { + spinnerIcon: 'spinner-icon mvx:w-4 mvx:h-4 mvx:fill-secondary-text mvx:animate-spinner' +} satisfies Record; + +export const SpinnerIcon = ({ class: className }: { class?: string }) => ( + + + +); + + diff --git a/src/common/Icon/components/SpinnerIcon/index.ts b/src/common/Icon/components/SpinnerIcon/index.ts new file mode 100644 index 00000000..2a092c9d --- /dev/null +++ b/src/common/Icon/components/SpinnerIcon/index.ts @@ -0,0 +1 @@ +export * from './SpinnerIcon'; \ No newline at end of file diff --git a/src/common/Icon/icon.types.ts b/src/common/Icon/icon.types.ts index 34c5f645..c699f9a0 100644 --- a/src/common/Icon/icon.types.ts +++ b/src/common/Icon/icon.types.ts @@ -24,7 +24,9 @@ export enum IconNamesEnum { circleCheck = 'circle-check', circleInfo = 'circle-info', coins = 'coins', - arrowsRotate = 'arrows-rotate' + arrowsRotate = 'arrows-rotate', + spinner = 'spinner', + arrowRight = 'arrow-right' } export type IconPropsType = JSXBase.IntrinsicElements['svg'] & { diff --git a/src/common/Tooltip/Tooltip.tsx b/src/common/Tooltip/Tooltip.tsx new file mode 100644 index 00000000..6a7b85b8 --- /dev/null +++ b/src/common/Tooltip/Tooltip.tsx @@ -0,0 +1,87 @@ +import { h } from '@stencil/core'; + +// prettier-ignore +const styles = { + tooltip: 'tooltip mvx:flex mvx:relative', + tooltipContentWrapper: 'tooltip-content-wrapper mvx:left-1/2 mvx:absolute mvx:z-1 mvx:transform mvx:-translate-x-1/2', + tooltipContent: 'tooltip-content mvx:flex-row mvx:cursor-default mvx:p-2 mvx:whitespace-nowrap mvx:text-xs mvx:rounded-xl mvx:leading-none mvx:bg-surface mvx:border-outline-variant mvx:border mvx:text-primary mvx:after:left-1/2 mvx:after:origin-center mvx:after:w-2 mvx:after:h-2 mvx:after:absolute mvx:after:border mvx:after:border-outline-variant mvx:after:bg-surface mvx:after:translate-x-[calc(50%-8px)] mvx:after:-rotate-[45deg] mvx:after:content-[""]', + tooltipContentTop: 'tooltip-content-top mvx:after:border-t-0 mvx:after:border-r-0 mvx:after:-bottom-1', + tooltipContentBottom: 'tooltip-content-bottom mvx:after:border-b-0 mvx:after:border-l-0 mvx:after:-top-1' +} satisfies Record; + +interface TooltipPropsType { + position?: 'top' | 'bottom'; + triggerOnClick?: boolean; + trigger: HTMLElement; + class?: string; + isTooltipVisible?: boolean; + onVisibilityChange?: (isTooltipVisible: boolean) => void; +} + +export function Tooltip({ position = 'top', triggerOnClick = false, trigger, class: className, onVisibilityChange, isTooltipVisible = false }: TooltipPropsType, children?: any) { + const setTooltipVisible = (isTooltipVisible: boolean) => { + onVisibilityChange?.(isTooltipVisible); + } + + const handleEllipsisClick = (event: MouseEvent) => { + if (!triggerOnClick) { + return; + } + + event.preventDefault(); + setTooltipVisible(!isTooltipVisible); + } + + const handleFocusOut = (event: FocusEvent) => { + const relatedTarget = event.relatedTarget as Node; + const currentTarget = event.currentTarget as HTMLElement; + + if (!currentTarget.contains(relatedTarget)) { + setTooltipVisible(false); + } + } + + const handleMouseEvent = (isTooltipVisible: boolean) => { + if (triggerOnClick) { + return; + } + + return (event: MouseEvent) => { + event.preventDefault(); + setTooltipVisible(isTooltipVisible); + }; + } + + return ( +
+ {isTooltipVisible && ( +
+
event.stopPropagation()} + > + {children} +
+
+ )} + + {trigger} +
+ ); +} \ No newline at end of file diff --git a/src/components.d.ts b/src/components.d.ts index 3d50c26c..a425cbc0 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -6,7 +6,7 @@ */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { IAddressTableData } from "./types/address-table.types"; -import { ButtonSizeEnum, ButtonVariantEnum } from "./components/visual/button/button.types"; +import { ButtonSizeEnum, ButtonVariantEnum } from "./common/Button/button.types"; import { CustomToastType, IComponentToast, ISimpleToast } from "./components/functional/toasts-list/components/transaction-toast/transaction-toast.type"; import { IConfirmScreenData, IConnectScreenData, ILedgerConnectPanelData } from "./components/functional/ledger-connect/ledger-connect.types"; import { IEventBus } from "./utils/EventBus"; @@ -20,7 +20,7 @@ import { TransactionRowType } from "./components/controlled/transactions-table/t import { IProviderBase } from "./types/provider.types"; import { IEventBus as IEventBus1, unknown as IWalletConnectPanelData } from "./components.d"; export { IAddressTableData } from "./types/address-table.types"; -export { ButtonSizeEnum, ButtonVariantEnum } from "./components/visual/button/button.types"; +export { ButtonSizeEnum, ButtonVariantEnum } from "./common/Button/button.types"; export { CustomToastType, IComponentToast, ISimpleToast } from "./components/functional/toasts-list/components/transaction-toast/transaction-toast.type"; export { IConfirmScreenData, IConnectScreenData, ILedgerConnectPanelData } from "./components/functional/ledger-connect/ledger-connect.types"; export { IEventBus } from "./utils/EventBus"; @@ -275,27 +275,8 @@ export namespace Components { */ "isToggled": boolean; } - interface MvxSignTransactionsFooter { - } interface MvxSignTransactionsHeader { } - interface MvxSignTransactionsOverview { - "action": string; - "amount": string; - "identifier": string; - "interactor": string; - "interactorIconUrl": string; - /** - * @default false - */ - "isApp": boolean; - /** - * @default '~$0.00078' - */ - "networkFee": string; - "tokenIconUrl": string; - "usdValue": string; - } interface MvxSignTransactionsPanel { "closeWithAnimation": () => Promise; "getEventBus": () => Promise; @@ -854,24 +835,12 @@ declare global { prototype: HTMLMvxSignTransactionsAdvancedDataDecodeElement; new (): HTMLMvxSignTransactionsAdvancedDataDecodeElement; }; - interface HTMLMvxSignTransactionsFooterElement extends Components.MvxSignTransactionsFooter, HTMLStencilElement { - } - var HTMLMvxSignTransactionsFooterElement: { - prototype: HTMLMvxSignTransactionsFooterElement; - new (): HTMLMvxSignTransactionsFooterElement; - }; interface HTMLMvxSignTransactionsHeaderElement extends Components.MvxSignTransactionsHeader, HTMLStencilElement { } var HTMLMvxSignTransactionsHeaderElement: { prototype: HTMLMvxSignTransactionsHeaderElement; new (): HTMLMvxSignTransactionsHeaderElement; }; - interface HTMLMvxSignTransactionsOverviewElement extends Components.MvxSignTransactionsOverview, HTMLStencilElement { - } - var HTMLMvxSignTransactionsOverviewElement: { - prototype: HTMLMvxSignTransactionsOverviewElement; - new (): HTMLMvxSignTransactionsOverviewElement; - }; interface HTMLMvxSignTransactionsPanelElement extends Components.MvxSignTransactionsPanel, HTMLStencilElement { } var HTMLMvxSignTransactionsPanelElement: { @@ -1111,9 +1080,7 @@ declare global { "mvx-sign-transactions-advanced": HTMLMvxSignTransactionsAdvancedElement; "mvx-sign-transactions-advanced-data": HTMLMvxSignTransactionsAdvancedDataElement; "mvx-sign-transactions-advanced-data-decode": HTMLMvxSignTransactionsAdvancedDataDecodeElement; - "mvx-sign-transactions-footer": HTMLMvxSignTransactionsFooterElement; "mvx-sign-transactions-header": HTMLMvxSignTransactionsHeaderElement; - "mvx-sign-transactions-overview": HTMLMvxSignTransactionsOverviewElement; "mvx-sign-transactions-panel": HTMLMvxSignTransactionsPanelElement; "mvx-simple-toast": HTMLMvxSimpleToastElement; "mvx-spinner-icon": HTMLMvxSpinnerIconElement; @@ -1390,27 +1357,8 @@ declare namespace LocalJSX { */ "isToggled"?: boolean; } - interface MvxSignTransactionsFooter { - } interface MvxSignTransactionsHeader { } - interface MvxSignTransactionsOverview { - "action"?: string; - "amount"?: string; - "identifier"?: string; - "interactor"?: string; - "interactorIconUrl"?: string; - /** - * @default false - */ - "isApp"?: boolean; - /** - * @default '~$0.00078' - */ - "networkFee"?: string; - "tokenIconUrl"?: string; - "usdValue"?: string; - } interface MvxSignTransactionsPanel { } interface MvxSimpleToast { @@ -1597,9 +1545,7 @@ declare namespace LocalJSX { "mvx-sign-transactions-advanced": MvxSignTransactionsAdvanced; "mvx-sign-transactions-advanced-data": MvxSignTransactionsAdvancedData; "mvx-sign-transactions-advanced-data-decode": MvxSignTransactionsAdvancedDataDecode; - "mvx-sign-transactions-footer": MvxSignTransactionsFooter; "mvx-sign-transactions-header": MvxSignTransactionsHeader; - "mvx-sign-transactions-overview": MvxSignTransactionsOverview; "mvx-sign-transactions-panel": MvxSignTransactionsPanel; "mvx-simple-toast": MvxSimpleToast; "mvx-spinner-icon": MvxSpinnerIcon; @@ -1669,9 +1615,7 @@ declare module "@stencil/core" { "mvx-sign-transactions-advanced": LocalJSX.MvxSignTransactionsAdvanced & JSXBase.HTMLAttributes; "mvx-sign-transactions-advanced-data": LocalJSX.MvxSignTransactionsAdvancedData & JSXBase.HTMLAttributes; "mvx-sign-transactions-advanced-data-decode": LocalJSX.MvxSignTransactionsAdvancedDataDecode & JSXBase.HTMLAttributes; - "mvx-sign-transactions-footer": LocalJSX.MvxSignTransactionsFooter & JSXBase.HTMLAttributes; "mvx-sign-transactions-header": LocalJSX.MvxSignTransactionsHeader & JSXBase.HTMLAttributes; - "mvx-sign-transactions-overview": LocalJSX.MvxSignTransactionsOverview & JSXBase.HTMLAttributes; "mvx-sign-transactions-panel": LocalJSX.MvxSignTransactionsPanel & JSXBase.HTMLAttributes; "mvx-simple-toast": LocalJSX.MvxSimpleToast & JSXBase.HTMLAttributes; "mvx-spinner-icon": LocalJSX.MvxSpinnerIcon & JSXBase.HTMLAttributes; diff --git a/src/components/common/button/button.scss b/src/components/common/button/button.scss new file mode 100644 index 00000000..ff1190ee --- /dev/null +++ b/src/components/common/button/button.scss @@ -0,0 +1 @@ +// This is needed to trigger the Stecil Tailwind compilation for inline Tailwind classes. \ No newline at end of file diff --git a/src/components/visual/button/button.stories.tsx b/src/components/common/button/button.stories.tsx similarity index 95% rename from src/components/visual/button/button.stories.tsx rename to src/components/common/button/button.stories.tsx index 23a1b35a..6232d65c 100644 --- a/src/components/visual/button/button.stories.tsx +++ b/src/components/common/button/button.stories.tsx @@ -5,7 +5,7 @@ import type { Meta, StoryObj } from '@stencil/storybook-plugin'; import capitalize from 'lodash.capitalize'; import type { Button } from './button'; -import { ButtonSizeEnum, ButtonVariantEnum } from './button.types'; +import { ButtonSizeEnum, ButtonVariantEnum } from '../../../common/Button/button.types'; // prettier-ignore const styles = { diff --git a/src/components/visual/button/button.tsx b/src/components/common/button/button.tsx similarity index 57% rename from src/components/visual/button/button.tsx rename to src/components/common/button/button.tsx index 40967113..ebcc07aa 100644 --- a/src/components/visual/button/button.tsx +++ b/src/components/common/button/button.tsx @@ -1,7 +1,9 @@ import type { EventEmitter } from '@stencil/core'; import { Component, Event, h, Prop } from '@stencil/core'; -import type { ButtonSizeEnum, ButtonVariantEnum } from './button.types'; +import type { ButtonSizeEnum, ButtonVariantEnum } from '../../../common/Button/button.types'; + +import { Button as ButtonComponent } from 'common/Button/Button'; @Component({ tag: 'mvx-button', @@ -19,20 +21,15 @@ export class Button { render() { return ( - + ); } } diff --git a/src/components/visual/copy-button/copy-button.stories.tsx b/src/components/common/copy-button/copy-button.stories.tsx similarity index 100% rename from src/components/visual/copy-button/copy-button.stories.tsx rename to src/components/common/copy-button/copy-button.stories.tsx diff --git a/src/components/common/copy-button/copy-button.tsx b/src/components/common/copy-button/copy-button.tsx new file mode 100644 index 00000000..a48c177d --- /dev/null +++ b/src/components/common/copy-button/copy-button.tsx @@ -0,0 +1,30 @@ +import { Component, Prop, State, h } from '@stencil/core'; +import { CopyButton as CopyButtonComponent } from 'common/CopyButton/CopyButton'; +import { CopyButtonHandler } from 'common/CopyButton/CopyButtonHandler'; + +@Component({ + tag: 'mvx-copy-button', + shadow: false, +}) +export class CopyButton { + @State() isSuccess: boolean = false; + @Prop() iconClass?: string; + @Prop() class?: string; + @Prop() text: string; + + private handleClick = CopyButtonHandler({ + onSuccessChange: (isSuccess) => (this.isSuccess = isSuccess), + }); + + render() { + return ( + + ); + } +} diff --git a/src/components/visual/copy-button/tests/copy-button.spec.ts b/src/components/common/copy-button/tests/copy-button.spec.ts similarity index 93% rename from src/components/visual/copy-button/tests/copy-button.spec.ts rename to src/components/common/copy-button/tests/copy-button.spec.ts index d10b193b..7422d672 100644 --- a/src/components/visual/copy-button/tests/copy-button.spec.ts +++ b/src/components/common/copy-button/tests/copy-button.spec.ts @@ -47,8 +47,8 @@ describe('CopyButton', () => { }); const copyButton = page.root; - const component = page.rootInstance as CopyButton; - await component.handleClick(new MouseEvent('click') as any); + const element = copyButton.querySelector('div'); + element.click(); await page.waitForChanges(); expect(copyButton).toEqualHtml(` @@ -71,8 +71,8 @@ describe('CopyButton', () => { }); const copyButton = page.root; - const component = page.rootInstance as CopyButton; - await component.handleClick(new MouseEvent('click') as any); + const element = copyButton.querySelector('div'); + element.click(); await page.waitForChanges(); expect(copyButton).toEqualHtml(` @@ -97,8 +97,9 @@ describe('CopyButton', () => { stopPropagation: jest.fn(), }; - const component = page.rootInstance as CopyButton; - await component.handleClick(mockEvent as any); + const element = page.root.querySelector('div'); + element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); + await page.waitForChanges(); expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1); expect(mockEvent.stopPropagation).toHaveBeenCalledTimes(1); diff --git a/src/components/common/tooltip/tooltip.scss b/src/components/common/tooltip/tooltip.scss new file mode 100644 index 00000000..ff1190ee --- /dev/null +++ b/src/components/common/tooltip/tooltip.scss @@ -0,0 +1 @@ +// This is needed to trigger the Stecil Tailwind compilation for inline Tailwind classes. \ No newline at end of file diff --git a/src/components/common/tooltip/tooltip.tsx b/src/components/common/tooltip/tooltip.tsx new file mode 100644 index 00000000..09b19046 --- /dev/null +++ b/src/components/common/tooltip/tooltip.tsx @@ -0,0 +1,38 @@ +import { Component, Event, EventEmitter, h, Prop, State } from '@stencil/core'; +import { Tooltip as TooltipComponent } from 'common/Tooltip/Tooltip'; + +@Component({ + tag: 'mvx-tooltip', + styleUrl: 'tooltip.scss', + shadow: true, +}) +export class Tooltip { + @Prop() position: 'top' | 'bottom' = 'top'; + @Prop() triggerOnClick?: boolean = false; + @Prop() trigger: HTMLElement; + @Prop() class?: string; + + @Event() triggerRender: EventEmitter; + @State() private isVisible: boolean = false; + + private handleVisibilityChange = (isVisible: boolean) => { + this.triggerRender.emit(isVisible); + this.isVisible = isVisible; + }; + + + render() { + return ( + + + + ); + } +} diff --git a/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/SignTransactionsFooter.tsx b/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/SignTransactionsFooter.tsx new file mode 100644 index 00000000..53450d8e --- /dev/null +++ b/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/SignTransactionsFooter.tsx @@ -0,0 +1,170 @@ +import { Fragment, h } from '@stencil/core'; +import classNames from 'classnames'; +import { Icon } from 'common/Icon'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +import state from '../../signTransactionsPanelStore'; +import styles from './signTransactionsFooter.styles'; +import { CopyButton } from 'common/CopyButton/CopyButton'; +import { Tooltip } from 'common/Tooltip/Tooltip'; +import { Button } from 'common/Button/Button'; +import { ExplorerLink } from 'common/ExplorerLink/ExplorerLink'; +import { Trim } from 'common/Trim/Trim'; + +let isWaitingForSignature: boolean = false; +let lastCommonData = { ...state.commonData }; + +interface SignTransactionsFooterPropsType { + tooltipVisible?: boolean; + onTooltipVisibilityChange?: (visible: boolean) => void; + isSuccessOnCopy?: boolean; + handleCopyButtonClick?: (event: MouseEvent, text?: string) => void; +} + +export function SignTransactionsFooter({ tooltipVisible, onTooltipVisibilityChange, isSuccessOnCopy, handleCopyButtonClick }: SignTransactionsFooterPropsType) { + const currentCommonData = { ...state.commonData }; + const hasChanged = JSON.stringify(currentCommonData) !== JSON.stringify(lastCommonData); + + if (hasChanged && isWaitingForSignature) { + // Reset the waiting state when data changes + isWaitingForSignature = false; + } + + lastCommonData = currentCommonData; + + const handleSignClick = () => { + if (state.onConfirm) { + isWaitingForSignature = true; + state.onConfirm(); + } + }; + + const { onCancel, onBack, onNext } = state; + const { currentIndex, currentIndexToSign, needsSigning, username, address, explorerLink, providerName } = + state.commonData; + + const isFirstTransaction = currentIndex === 0; + const currentIndexNeedsSigning = currentIndex === currentIndexToSign; + const currentIndexCannotBeSignedYet = currentIndex > currentIndexToSign; + const showForwardAction = currentIndexNeedsSigning || currentIndexCannotBeSignedYet; + const checkButtonText = providerName ? `Check ${providerName}` : 'Check your device'; + + return ( +
+
+
+
+ +
+ +
+ {currentIndexCannotBeSignedYet && ( +
event.stopPropagation()} + > + + } + > + {needsSigning ? ( + + You cannot sign this transaction yet,
please go back and sign consecutively. +
+ ) : ( + + You cannot confirm this transaction yet,
+ please go back and confirm consecutively. +
+ )} +
+
+ )} + + +
+
+ +
+
Sign with
+ + {username && ( +
+ @ + {username} +
+ )} + + {!username && address && ( + < Trim + text={address} + class={styles.signTransactionsFooterIdentityAddress} + data-testid={DataTestIdsEnum.signTransactionsFooterIdentityAddress} + /> + )} + + handleCopyButtonClick?.(event, username ?? address)} /> + +
+
+
+ ); +} diff --git a/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/signTransactionsFooter.styles.ts b/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/signTransactionsFooter.styles.ts new file mode 100644 index 00000000..6451208a --- /dev/null +++ b/src/components/functional/sign-transactions-panel/components/SignTransactionsFooter/signTransactionsFooter.styles.ts @@ -0,0 +1,21 @@ +export default { + signTransactionsFooterContainer: 'sign-transactions-footer-container mvx:flex mvx:flex-col mvx:flex-1 mvx:h-full', + signTransactionsFooter: 'sign-transactions-footer mvx:mt-auto mvx:flex mvx:flex-col mvx:items-center mvx:pt-5 mvx:gap-5 mvx:text-center', + signTransactionsFooterButtons: 'sign-transactions-footer-buttons mvx:flex mvx:items-center mvx:w-full mvx:gap-3', + signTransactionsFooterButtonWrapper: 'sign-transactions-footer-button-wrapper mvx:relative mvx:flex mvx:flex-col mvx:flex-1', + signTransactionsFooterButtonWrapperCancel: 'sign-transactions-footer-button-wrapper-cancel mvx:w-32 mvx:max-w-32', + signTransactionsFooterButtonTooltipWrapper: 'sign-transactions-footer-button-tooltip-wrapper mvx:absolute mvx:inset-0', + signTransactionsFooterButton: 'sign-transactions-footer-button mvx:flex', + signTransactionsFooterButtonIcon: 'sign-transactions-footer-button-icon mvx:flex mvx:transition-all mvx:duration-200 mvx:ease-in-out', + signTransactionsFooterButtonIconLighter: 'sign-transactions-footer-button-icon-lighter mvx:fill-secondary-text', + signTransactionsFooterIdentity: 'sign-transactions-footer-identity mvx:max-w-64 mvx:flex mvx:items-center mvx:text-base mvx:gap-2 mvx:overflow-hidden', + signTransactionsFooterIdentityLabel: 'sign-transactions-footer-identity-label mvx:whitespace-nowrap mvx:text-secondary-text', + signTransactionsFooterIdentityAddress: 'sign-transactions-footer-identity-address mvx:text-primary', + signTransactionsTrimWrapper: 'sign-transactions-trim-wrapper mvx:items-end mvx:leading-none', + signTransactionsFooterIdentityUsername: 'sign-transactions-footer-identity-username mvx:flex mvx:items-center mvx:text-base mvx:text-primary', + signTransactionsFooterIdentityUsernamePrefix: 'sign-transactions-footer-identity-username-prefix mvx:text-accent', + signTransactionsFooterIdentityCopy: 'sign-transactions-footer-identity-copy mvx:text-primary', + signTransactionsButtonTooltip: 'sign-transactions-button-tooltip mvx:absolute mvx:top-0 mvx:h-12 mvx:left-0 mvx:right-0', + signTransactionsActionButton: 'sign-transactions-action-button mvx:text-base! mvx:w-full', + signTransactionsExplorerLinkIcon: 'sign-transactions-explorer-link-icon mvx:text-primary', +} satisfies Record; \ No newline at end of file diff --git a/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/SignTransactionsOverview.tsx b/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/SignTransactionsOverview.tsx new file mode 100644 index 00000000..2cc83207 --- /dev/null +++ b/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/SignTransactionsOverview.tsx @@ -0,0 +1,115 @@ +import { h } from '@stencil/core'; +import { Icon } from 'common/Icon'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +import { handleAmountResize } from '../../helpers'; +import classNames from 'classnames'; + +import styles from './signTransactionsOverview.styles'; +import { Trim } from 'common/Trim/Trim'; + +interface SignTransactionsOverviewPropsType { + identifier: string; + usdValue: string; + amount: string; + tokenIconUrl: string; + interactor: string; + interactorIconUrl: string; + action: string; + networkFee: string; + isApp: boolean; +} + +export function SignTransactionsOverview({ identifier, usdValue, amount, tokenIconUrl, interactor, interactorIconUrl, action, networkFee = '~$0.00078', isApp = false }: SignTransactionsOverviewPropsType) { + const setAmountValueRef = (el?: HTMLElement) => { + if (!el) { + return; + } + + requestAnimationFrame(() => handleAmountResize(el)); + }; + + return ( +
+
+
+
{isApp ? 'Amount' : 'Send'}
+
+
+
+ + {amount} {identifier} + +
+ {identifier !== 'USD' && ( +
+ {usdValue} +
+ )} +
+ {tokenIconUrl && ( +
+ {identifier} +
+ )} +
+
+ +
+
+ + + + +
+
+ +
+
{isApp ? 'App' : 'To'}
+
+ {interactorIconUrl && ( +
+ {interactor} +
+ )} + {interactor && ( + + )} +
+
+ {isApp && ( +
+
Action
+
+ {action} +
+
+ )} +
+ +
+
+
+ Network Fee +
+
+
+ {networkFee} +
+
+
+
+ ); +} + diff --git a/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/signTransactionsOverview.styles.ts b/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/signTransactionsOverview.styles.ts new file mode 100644 index 00000000..65db8e5d --- /dev/null +++ b/src/components/functional/sign-transactions-panel/components/SignTransactionsOverview/signTransactionsOverview.styles.ts @@ -0,0 +1,33 @@ +// prettier-ignore +export default { + signTransactionsOverviewContainer: 'sign-transactions-overview-container mvx:flex mvx:flex-col mvx:w-full mvx:bg-accent-variant mvx:rounded-2xl mvx:overflow-hidden', + signTransactionsOverviewContent: 'sig-transactions-overview-content mvx:flex mvx:flex-col mvx:gap-1 mvx:p-1 mvx:relative', + signTransactionsDetailRow: 'sign-transactions-detail-row mvx:flex mvx:items-center mvx:py-4 mvx:px-5 mvx:bg-secondary mvx:relative mvx:overflow-hidden mvx:min-h-20', + signTransactionsAmountRow: 'sign-transactions-amount-row mvx:rounded-xl', + signTransactionsInteractorRow: 'sign-transactions-interactor-row mvx:rounded-xl mvx:relative mvx:z-5', + signTransactionsActionRow: 'sign-transactions-action-row mvx:pt-7 mvx:px-5 mvx:pb-2 mvx:rounded-bl-xl mvx:min-h-14 mvx:-mt-6 mvx:relative mvx:before:absolute mvx:before:opacity-60 mvx:before:top-0 mvx:before:bottom-0 mvx:before:left-0 mvx:before:right-0 mvx:before:transition-all mvx:before:duration-200 mvx:before:rounded-b-xl mvx:before:pointer-events-none mvx:before:ease-in-out mvx:before:content-[""] mvx:before:bg-tertiary', + signTransactionsDetailLabel: 'sign-transactions-detail-label mvx:text-secondary-text mvx:text-sm mvx:font-normal mvx:leading-5 mvx:relative mvx:z-1', + signTransactionsAmountDisplay: 'sign-transactions-amount-display mvx:flex mvx:items-center mvx:ml-auto mvx:gap-3 mvx:z-1', + signTransactionsAmountValueContainer: 'sign-transactions-amount-value-container mvx:flex mvx:flex-col mvx:items-end mvx:gap-1', + signTransactionsAmountValue: 'sign-transactions-amount-value mvx:text-xl mvx:font-medium mvx:text-primary mvx:-tracking-[0.24px] mvx:leading-6 mvx:text-right mvx:whitespace-nowrap', + signTransactionsIdentifier: 'sign-transactions-identifier mvx:text-sm mvx:text-primary', + signTransactionsUsdValue: 'sign-transactions-usd-value mvx:text-sm mvx:text-secondary-text', + signTransactionsTokenIcon: 'sign-transactions-token-icon mvx:w-10 mvx:h-10 mvx:rounded-xs mvx:overflow-hidden mvx:border mvx:border-neutral-700', + signTransactionsTokenIconImg: 'sign-transactions-token-icon-img mvx:w-full mvx:h-full mvx:object-cover', + signTransactionsInteractorInfo: 'sign-transactions-interactor-info mvx:flex mvx:items-center mvx:ml-auto mvx:gap-3 mvx:z-1', + signTransactionsInteractorIcon: 'sign-transactions-interactor-icon mvx:w-6 mvx:h-6 mvx:rounded-full mvx:overflow-hidden', + signTransactionsInteractorIconImg: 'sign-transactions-interactor-icon-img mvx:w-full mvx:h-full mvx:object-cover', + signTransactionsInteractorName: 'sign-transactions-interactor-name mvx:text-base mvx:font-normal mvx:text-primary mvx:-tracking-[0.16px] mvx:leading-5 mvx:!max-w-65', + signTransactionsDirection: 'sign-transactions-direction mvx:absolute mvx:flex mvx:flex-col mvx:items-center mvx:justify-center mvx:w-6 mvx:h-6 mvx:rounded-full mvx:left-5 mvx:z-10 mvx:bg-accent-variant', + signTransactionsDirectionIcon: 'sign-transactions-direction-icon mvx:flex mvx:flex-col mvx:items-center mvx:gap-px', + signTransactionsDirectionIconArrow: 'sign-transactions-direction-icon-arrow mvx:flex mvx:items-center mvx:justify-center mvx:relative mvx:shrink-0 mvx:w-3 mvx:basis-auto mvx:-mt-1 mvx:-mb-0.5 mvx:text-secondary-text', + signTransactionsDirectionIconArrowDown: 'sign-transactions-direction-icon-arrow-down mvx:-mt-0.5 mvx:-mb-1 mvx:order-1', + signTransactionsDirectionIconDot: 'sign-transactions-direction-icon-dot mvx:relative mvx:rounded-full mvx:w-0.5 mvx:h-0.5 mvx:bg-secondary-text', + signTransactionsActionValue: 'sign-transactions-action-value mvx:ml-auto mvx:text-sm mvx:font-medium mvx:text-secondary-text mvx:relative mvx:z-1', + signTransactionsFeeContainer: 'sign-transactions-fee-container mvx:py-2 mvx:px-6', + signTransactionsFeeRow: 'sign-transactions-fee-row mvx:flex mvx:items-center mvx:justify-between mvx:py-3 mvx:px-0', + signTransactionsFeeLabelContainer: 'sign-transactions-fee-label-container mvx:flex mvx:items-center mvx:gap-1', + signTransactionsFeeLabel: 'sign-transactions-fee-label mvx:text-secondary-text mvx:text-sm mvx:font-normal mvx:leading-5', + signTransactionsInfoIcon: 'sign-transactions-info-icon mvx:w-3.5 mvx:h-3.5 mvx:relative mvx:before:content-["ⓘ"] mvx:before:text-sm mvx:before:text-secondary-text mvx:before:absolute mvx:before:-top-0.5 mvx:before:left-0', + signTransactionsFeeValue: 'sign-transactions-fee-value mvx:text-primary mvx:text-sm mvx:font-normal mvx:leading-5' +} satisfies Record; \ No newline at end of file diff --git a/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.scss b/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.scss deleted file mode 100644 index 6b3ce95f..00000000 --- a/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.scss +++ /dev/null @@ -1,67 +0,0 @@ -:host { - @apply mvx:flex mvx:flex-col mvx:flex-1; - - .sign-transactions-footer { - @apply mvx:mt-auto mvx:flex mvx:flex-col mvx:items-center mvx:pt-5 mvx:gap-5 mvx:text-center; - - .sign-transactions-footer-buttons { - @apply mvx:flex mvx:items-center mvx:w-full mvx:gap-3; - - .sign-transactions-footer-button-wrapper { - @apply mvx:relative mvx:flex mvx:flex-col mvx:flex-1; - - &.cancel { - @apply mvx:w-32 mvx:max-w-32; - } - - .sign-transactions-footer-button-tooltip-wrapper { - @apply mvx:absolute mvx:inset-0; - } - - .sign-transactions-footer-button { - @apply mvx:flex; - - .sign-transactions-footer-button-icon { - @apply mvx:flex mvx:transition-all mvx:duration-200 mvx:ease-in-out; - - &.lighter { - fill: var(--mvx-text-color-secondary); - } - } - } - } - } - - .sign-transactions-footer-identity { - @apply mvx:max-w-64 mvx:flex mvx:items-center mvx:text-base mvx:gap-2; - - .sign-transactions-footer-identity-label { - @apply mvx:whitespace-nowrap; - color: var(--mvx-text-color-secondary); - } - - .sign-transactions-footer-identity-address { - @apply mvx:relative mvx:min-w-0 mvx:max-w-none; - top: 0.25px; - color: var(--mvx-text-color-primary); - - .trim-wrapper { - @apply mvx:items-end mvx:leading-none; - } - } - - .sign-transactions-footer-identity-username { - @apply mvx:flex mvx:items-center mvx:text-base; - color: var(--mvx-text-color-primary); - - .sign-transactions-footer-identity-username-prefix { - color: var(--mvx-text-accent-color); - } - } - - .sign-transactions-footer-identity-copy { - color: var(--mvx-text-color-primary); - } - } - } -} diff --git a/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.tsx b/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.tsx deleted file mode 100644 index 2bc8a967..00000000 --- a/src/components/functional/sign-transactions-panel/components/sign-transactions-footer/sign-transactions-footer.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { Component, Fragment, h, State } from '@stencil/core'; -import classNames from 'classnames'; -import { Icon } from 'common/Icon'; -import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; - -import state from '../../signTransactionsPanelStore'; - -const signTransactionsFooterClasses: Record = { - buttonTooltip: 'mvx:absolute mvx:top-0 mvx:h-12 mvx:left-0 mvx:right-0', - actionButton: 'mvx:text-base! mvx:w-full', - explorerLinkIcon: 'mvx:fill-link!', -}; - -@Component({ - tag: 'mvx-sign-transactions-footer', - styleUrl: 'sign-transactions-footer.scss', - shadow: true, -}) -export class SignTransactionsFooter { - @State() awaitsExternalConfirmation: boolean = false; - @State() isWaitingForSignature: boolean = false; - @State() lastCommonData = { ...state.commonData }; - - componentWillLoad() { - this.lastCommonData = { ...state.commonData }; - } - - componentWillRender() { - const currentCommonData = { ...state.commonData }; - const hasChanged = JSON.stringify(currentCommonData) !== JSON.stringify(this.lastCommonData); - - if (hasChanged && this.isWaitingForSignature) { - // Reset the waiting state when data changes - this.isWaitingForSignature = false; - } - - this.lastCommonData = currentCommonData; - } - - private handleSignClick = () => { - if (state.onConfirm) { - this.isWaitingForSignature = true; - state.onConfirm(); - } - }; - - render() { - const { onCancel, onBack, onNext } = state; - const { currentIndex, currentIndexToSign, needsSigning, username, address, explorerLink, providerName } = - state.commonData; - - const isFirstTransaction = currentIndex === 0; - const currentIndexNeedsSigning = currentIndex === currentIndexToSign; - const currentIndexCannotBeSignedYet = currentIndex > currentIndexToSign; - const showForwardAction = currentIndexNeedsSigning || currentIndexCannotBeSignedYet; - const checkButtonText = providerName ? `Check ${providerName}` : 'Check your device'; - - return ( - - ); - } -} diff --git a/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.scss b/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.scss deleted file mode 100644 index 06987750..00000000 --- a/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.scss +++ /dev/null @@ -1,224 +0,0 @@ -/* Transaction Overview container */ -.overview-container { - display: flex; - flex-direction: column; - width: 100%; - background: var(--mvx-bg-accent-variant-color); - border-radius: 16px; - overflow: hidden; -} - -.overview-content { - display: flex; - flex-direction: column; - gap: 4px; - padding: 4px; - position: relative; -} - -/* Common row styles */ -.detail-row { - display: flex; - align-items: center; - padding: 16px 20px; - background: var(--mvx-bg-color-secondary); - position: relative; - overflow: hidden; - min-height: 80px; -} - -.amount-row { - border-radius: 12px; -} - -.interactor-row { - border-radius: 12px; - position: relative; - z-index: 5; -} - -.action-row { - padding: 28px 20px 8px 20px; - border-radius: 0 0 12px 12px; - min-height: 56px; - margin-top: -24px; - position: relative; - - &:before { - @apply mvx:absolute mvx:opacity-60 mvx:top-0 mvx:bottom-0 mvx:left-0 mvx:right-0 mvx:transition-all mvx:duration-200; - @apply mvx:rounded-b-xl mvx:pointer-events-none mvx:ease-in-out; - content: ''; - background: var(--mvx-bg-color-tertiary); - } -} - -.detail-label { - color: var(--mvx-text-color-secondary); - font-size: 14px; - font-weight: 400; - line-height: 20px; - position: relative; - z-index: 1; -} - -/* Amount section */ -.amount-display { - display: flex; - align-items: center; - margin-left: auto; - gap: 12px; - z-index: 1; -} - -.amount-value-container { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 4px; -} - -.amount-value { - font-size: 24px; - font-weight: 500; - color: var(--mvx-text-color-primary); - letter-spacing: -0.24px; - line-height: 24px; - text-align: right; - white-space: nowrap; -} - -.identifier { - font-size: 14px; - color: var(--mvx-text-color-primary); -} - -.usd-value { - font-size: 14px; - color: var(--mvx-text-color-secondary); -} - -.token-icon { - width: 40px; - height: 40px; - border-radius: 2px; - overflow: hidden; - border: 1px solid var(--mvx-neutral-700); -} - -.token-icon img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* Interactor section */ -.interactor-info { - display: flex; - align-items: center; - margin-left: auto; - gap: 12px; - z-index: 1; -} - -.interactor-icon { - width: 24px; - height: 24px; - border-radius: 50%; - overflow: hidden; -} - -.interactor-icon img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.interactor-name { - font-size: 16px; - font-weight: 400; - color: var(--mvx-text-color-primary); - letter-spacing: -0.16px; - line-height: 22px; - max-width: 300px; -} - -.sign-transactions-direction { - @apply mvx:absolute mvx:flex mvx:flex-col mvx:items-center mvx:justify-center mvx:w-6 mvx:h-6 mvx:rounded-full mvx:left-5 mvx:z-10; - background: var(--mvx-bg-accent-variant-color); - top: calc(50% - 12px); - - .sign-transactions-direction-icon { - @apply mvx:flex mvx:flex-col mvx:items-center mvx:gap-px; - - .sign-transactions-direction-icon-arrow { - @apply mvx:flex mvx:items-center mvx:justify-center mvx:relative mvx:shrink-0 mvx:w-3 mvx:basis-auto mvx:-mt-1 mvx:-mb-0.5; - color: var(--mvx-text-color-secondary); - - &.down { - @apply mvx:-mt-0.5 mvx:-mb-1 mvx:order-1; - } - } - - .sign-transactions-direction-icon-dot { - @apply mvx:relative mvx:rounded-full mvx:w-0.5 mvx:h-0.5; - background-color: var(--mvx-text-color-secondary); - } - } -} - -/* Action section */ -.action-value { - margin-left: auto; - font-size: 14px; - font-weight: 500; - color: var(--mvx-text-color-secondary); - position: relative; - z-index: 1; -} - -/* Fee section */ -.fee-container { - padding: 8px 24px; -} - -.fee-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 0; -} - -.fee-label-container { - display: flex; - align-items: center; - gap: 4px; -} - -.fee-label { - color: var(--mvx-text-color-secondary); - font-size: 14px; - font-weight: 400; - line-height: 19.6px; -} - -.info-icon { - width: 14px; - height: 14px; - position: relative; -} - -.info-icon::before { - content: 'ⓘ'; - font-size: 14px; - color: var(--mvx-text-color-secondary); - position: absolute; - top: -2px; - left: 0; -} - -.fee-value { - color: var(--mvx-text-color-primary); - font-size: 14px; - font-weight: 400; - line-height: 20px; -} diff --git a/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.tsx b/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.tsx deleted file mode 100644 index 0c9289ac..00000000 --- a/src/components/functional/sign-transactions-panel/components/sign-transactions-overview/sign-transactions-overview.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { Component, h, Prop } from '@stencil/core'; -import { Icon } from 'common/Icon'; -import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; - -import { handleAmountResize } from '../../helpers'; - -@Component({ - tag: 'mvx-sign-transactions-overview', - styleUrl: 'sign-transactions-overview.scss', - shadow: true, -}) -export class SignTransactionsOverview { - @Prop() identifier: string; - @Prop() usdValue: string; - @Prop() amount: string; - @Prop() tokenIconUrl: string; - @Prop() interactor: string; - @Prop() interactorIconUrl: string; - @Prop() action: string; - @Prop() networkFee: string = '~$0.00078'; - @Prop() isApp: boolean = false; - - private amountValueRef: HTMLElement; - - componentDidRender() { - if (this.amountValueRef) { - requestAnimationFrame(() => handleAmountResize(this.amountValueRef)); - } - } - - render() { - return ( -
-
-
-
{this.isApp ? 'Amount' : 'Send'}
-
-
-
(this.amountValueRef = el)} - > - - {this.amount} {this.identifier} - -
- {this.identifier !== 'USD' && ( -
- {this.usdValue} -
- )} -
- {this.tokenIconUrl && ( -
- {this.identifier} -
- )} -
-
- -
-
- - - - -
-
- -
-
{this.isApp ? 'App' : 'To'}
-
- {this.interactorIconUrl && ( -
- {this.interactor} -
- )} - {this.interactor && ( - - )} -
-
- {this.isApp && ( -
-
Action
-
- {this.action} -
-
- )} -
- -
-
-
- Network Fee -
-
-
- {this.networkFee} -
-
-
-
- ); - } -} diff --git a/src/components/functional/sign-transactions-panel/helpers/handleAmountResize/handleAmountResize.ts b/src/components/functional/sign-transactions-panel/helpers/handleAmountResize/handleAmountResize.ts index 56d20f92..4ff63e98 100644 --- a/src/components/functional/sign-transactions-panel/helpers/handleAmountResize/handleAmountResize.ts +++ b/src/components/functional/sign-transactions-panel/helpers/handleAmountResize/handleAmountResize.ts @@ -7,7 +7,7 @@ export const handleAmountResize = (element: HTMLElement | null) => { const getFontSize = (element: HTMLElement) => parseInt(getComputedStyle(element).getPropertyValue('font-size')); const firstChild = element.firstChild as HTMLElement; - const maxWidth = 270; + const maxWidth = 250; const sizes = { parent: Math.min(element.offsetWidth, maxWidth), firstChild: getFontSize(firstChild), diff --git a/src/components/functional/sign-transactions-panel/sign-transactions-panel.scss b/src/components/functional/sign-transactions-panel/sign-transactions-panel.scss index ff99cd5c..8d7de2b8 100644 --- a/src/components/functional/sign-transactions-panel/sign-transactions-panel.scss +++ b/src/components/functional/sign-transactions-panel/sign-transactions-panel.scss @@ -113,3 +113,41 @@ .sign-transactions-panel { @apply mvx:flex mvx:flex-col mvx:flex-1 mvx:pb-6; } + +// Trim component base styles +.trim { + @apply mvx:flex mvx:relative mvx:max-w-full mvx:overflow-hidden mvx:whitespace-nowrap; +} + +.trim-full { + @apply mvx:text-transparent mvx:absolute mvx:leading-5; +} + +.trim-full-visible { + @apply mvx:text-inherit mvx:relative mvx:leading-5; +} + +.trim-wrapper { + @apply mvx:hidden; +} + +.trim-wrapper-visible { + @apply mvx:overflow-hidden mvx:max-w-full mvx:flex; +} + +.trim-left-wrapper { + @apply mvx:flex-shrink mvx:text-ellipsis mvx:overflow-hidden mvx:text-left mvx:text-[1px]; +} + +.trim-left { + @apply mvx:select-none mvx:pointer-events-none mvx:inline mvx:text-base mvx:leading-5; +} + +.trim-right-wrapper { + @apply mvx:flex-shrink mvx:text-ellipsis mvx:overflow-hidden mvx:whitespace-nowrap mvx:text-right mvx:text-[1px]; + direction: rtl; +} + +.trim-right { + @apply mvx:select-none mvx:pointer-events-none mvx:inline mvx:text-base mvx:leading-5 mvx:text-clip; +} \ No newline at end of file diff --git a/src/components/functional/sign-transactions-panel/sign-transactions-panel.tsx b/src/components/functional/sign-transactions-panel/sign-transactions-panel.tsx index ee57ac8c..0dc7ab2a 100644 --- a/src/components/functional/sign-transactions-panel/sign-transactions-panel.tsx +++ b/src/components/functional/sign-transactions-panel/sign-transactions-panel.tsx @@ -8,6 +8,21 @@ import { EventBus } from 'utils/EventBus'; import type { IOverviewProps, ISignTransactionsPanelData } from './sign-transactions-panel.types'; import { SignEventsEnum, TransactionTabsEnum } from './sign-transactions-panel.types'; import state, { resetState } from './signTransactionsPanelStore'; +import { SignTransactionsFooter } from './components/SignTransactionsFooter/SignTransactionsFooter'; +import { CopyButtonHandler } from 'common/CopyButton/CopyButtonHandler'; +import { SignTransactionsOverview } from './components/SignTransactionsOverview/SignTransactionsOverview'; + +// prettier-ignore +const styles = { + button: 'button mvx:flex mvx:items-center mvx:justify-center mvx:font-bold mvx:leading-none mvx:px-4 mvx:max-h-full mvx:rounded-xl mvx:cursor-pointer mvx:transition-all mvx:duration-200 mvx:ease-in-out mvx:gap-2', + buttonLarge: 'button-large mvx:h-12 mvx:text-base mvx:px-6', + buttonSmall: 'button-small mvx:h-10 mvx:text-xs mvx:rounded-xl', + buttonPrimary: 'button-primary mvx:text-button-primary mvx:bg-button-bg-primary mvx:border mvx:border-button-bg-primary', + buttonSecondary: 'button-secondary mvx:relative mvx:text-button-secondary mvx:border mvx:border-transparent mvx:after:absolute mvx:after:inset-0 mvx:after:rounded-lg mvx:after:opacity-40 mvx:after:transition-all mvx:after:duration-200 mvx:after:ease-in-out mvx:after:bg-button-bg-secondary mvx:after:content-[""] mvx:after:-z-1 mvx:hover:opacity-100 mvx:hover:text-button-primary mvx:hover:after:opacity-100 mvx:hover:after:bg-button-bg-primary', + buttonSecondarySmall: 'button-secondary-small mvx:after:rounded-xl', + buttonNeutral: 'button-neutral mvx:text-neutral-925 mvx:bg-white mvx:hover:opacity-75', + buttonDisabled: 'button-disabled mvx:pointer-events-none mvx:bg-transparent mvx:cursor-default mvx:border mvx:border-secondary-text mvx:!text-secondary-text mvx:hover:opacity-100' +} satisfies Record; @Component({ tag: 'mvx-sign-transactions-panel', @@ -27,6 +42,8 @@ export class SignTransactionsPanel { @State() isOpen: boolean = false; @State() activeTab: TransactionTabsEnum = TransactionTabsEnum.overview; + @State() isFooterTooltipVisible: boolean = false; + @State() isSuccessOnCopy: boolean = false; @Method() async getEventBus() { await this.connectionMonitor.waitForConnection(); @@ -114,7 +131,16 @@ export class SignTransactionsPanel { }; } + private handleIsFooterTooltipVisible = (isTooltipVisible: boolean) => { + this.isFooterTooltipVisible = isTooltipVisible; + }; + + private handleCopyButtonClick = CopyButtonHandler({ + onSuccessChange: (isSuccess) => (this.isSuccessOnCopy = isSuccess), + }); + render() { + console.log(styles) //TODO: remove this const transactionTabs = Object.values(TransactionTabsEnum); const { commonData } = state; @@ -146,13 +172,18 @@ export class SignTransactionsPanel { {this.activeTab === TransactionTabsEnum.overview ? ( - + ) : ( )} - + ); diff --git a/src/components/visual/button/button.scss b/src/components/visual/button/button.scss deleted file mode 100644 index 0085031b..00000000 --- a/src/components/visual/button/button.scss +++ /dev/null @@ -1,69 +0,0 @@ -:host { - @apply mvx:flex; - - .button { - @apply mvx:flex mvx:items-center mvx:justify-center mvx:font-bold mvx:leading-none mvx:px-4 mvx:max-h-full; - @apply mvx:rounded-xl mvx:cursor-pointer mvx:transition-all mvx:duration-200 mvx:ease-in-out mvx:gap-2; - - &.large { - @apply mvx:h-12 mvx:text-base mvx:px-6; - } - - &.small { - @apply mvx:h-10 mvx:text-xs mvx:rounded-xl; - } - - &.primary { - color: var(--mvx-button-text-primary); - background-color: var(--mvx-button-bg-primary); - border: 1px solid var(--mvx-button-bg-primary); - } - - &.secondary { - @apply mvx:relative; - color: var(--mvx-button-text-secondary); - border: 1px solid transparent; - - &.small:after { - @apply mvx:rounded-xl; - } - - &:after { - @apply mvx:absolute mvx:inset-0 mvx:rounded-lg mvx:opacity-40 mvx:transition-all mvx:duration-200 mvx:ease-in-out; - background-color: var(--mvx-button-bg-secondary); - content: ''; - z-index: -1; - } - - &:hover { - @apply mvx:opacity-100; - color: var(--mvx-button-text-primary); - - &:after { - @apply mvx:opacity-100; - background-color: var(--mvx-button-bg-primary); - } - } - } - - &.neutral { - color: var(--mvx-neutral-925); - background: var(--mvx-white); - } - - &:hover { - @apply mvx:opacity-75; - } - - &.disabled, - &:disabled { - @apply mvx:pointer-events-none mvx:bg-transparent mvx:cursor-default; - border: 1px solid var(--mvx-text-color-secondary); - color: var(--mvx-text-color-secondary); - - &:hover { - @apply mvx:opacity-100; - } - } - } -} diff --git a/src/components/visual/copy-button/copy-button.tsx b/src/components/visual/copy-button/copy-button.tsx deleted file mode 100644 index 693b696b..00000000 --- a/src/components/visual/copy-button/copy-button.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Component, h, Prop, State } from '@stencil/core'; -import { Icon } from 'common/Icon'; -import { copyToClipboard } from 'utils/copyToClipboard'; - -// prettier-ignore -const styles = { - copyButton: 'copy-button mvx:flex', - copyButtonIcon: 'copy-button-icon mvx:flex mvx:cursor-pointer mvx:justify-center mvx:transition-opacity mvx:duration-200 mvx:ease-in-out mvx:hover:opacity-80', - copyButtonIconCheck: 'copy-button-icon-check mvx:hover:opacity-100! mvx:cursor-default!', -} satisfies Record; - -@Component({ - tag: 'mvx-copy-button', - shadow: false, -}) -export class CopyButton { - private timeoutId: number | null = null; - - @State() isSuccess: boolean = false; - @Prop() iconClass?: string; - @Prop() class?: string; - @Prop() text: string; - - async handleClick(event: MouseEvent) { - const trimmedText = this.text ? this.text.trim() : this.text; - const success = await copyToClipboard(trimmedText); - - const setSuccessStateTo = (newSuccessState: boolean) => { - this.isSuccess = newSuccessState; - }; - - event.preventDefault(); - event.stopPropagation(); - setSuccessStateTo(success); - - if (success) { - this.timeoutId = window.setTimeout(() => setSuccessStateTo(false), 2000); - } - } - - disconnectedCallback() { - if (this.timeoutId) { - clearTimeout(this.timeoutId); - this.timeoutId = null; - } - } - - render() { - return ( -
- -
- ); - } -} diff --git a/src/components/visual/data-with-explorer-link/tests/data-with-explorer-link.spec.ts b/src/components/visual/data-with-explorer-link/tests/data-with-explorer-link.spec.ts index 9c6ecec1..f8ba557c 100644 --- a/src/components/visual/data-with-explorer-link/tests/data-with-explorer-link.spec.ts +++ b/src/components/visual/data-with-explorer-link/tests/data-with-explorer-link.spec.ts @@ -2,8 +2,8 @@ import { newSpecPage } from '@stencil/core/testing'; import { Trim } from 'common/Trim/Trim'; import { ExplorerLink } from '../../../common/explorer-link/explorer-link'; -import { CopyButton } from '../../copy-button/copy-button'; -import { Tooltip } from '../../tooltip/tooltip'; +import { CopyButton } from '../../../common/copy-button/copy-button'; +import { Tooltip } from '../../../common/tooltip/tooltip'; import { DataWithExplorerLink } from '../data-with-explorer-link'; describe('DataWithExplorerLink', () => { diff --git a/src/components/visual/tooltip/tooltip.scss b/src/components/visual/tooltip/tooltip.scss deleted file mode 100644 index 4ecce9e3..00000000 --- a/src/components/visual/tooltip/tooltip.scss +++ /dev/null @@ -1,40 +0,0 @@ -.tooltip { - @apply mvx:flex mvx:relative; - - .tooltip-content-wrapper { - @apply mvx:left-1/2 mvx:absolute mvx:z-1 mvx:transform mvx:-translate-x-1/2; - - &.top { - bottom: calc(100% + 16px); - } - - &.bottom { - top: calc(100% + 16px); - } - - .tooltip-content { - @apply mvx:flex-row mvx:cursor-default mvx:p-2 mvx:whitespace-nowrap mvx:text-xs mvx:rounded-xl mvx:leading-none; - background: var(--mvx-bg-color-primary); - border: 1px solid var(--mvx-border-color-secondary); - color: var(--mvx-text-color-primary); - - &:after { - @apply mvx:left-1/2 mvx:origin-center mvx:w-2 mvx:h-2 mvx:absolute; - border: 1px solid var(--mvx-border-color-secondary); - background-color: var(--mvx-bg-color-primary); - content: ''; - transform: translateX(calc(50% - 8px)) rotateZ(calc(45deg * -1)); - } - - &.top:after { - @apply mvx:border-t-0 mvx:border-r-0; - bottom: calc(4px * -1); - } - - &.bottom:after { - @apply mvx:border-b-0 mvx:border-l-0; - top: calc(4px * -1); - } - } - } -} diff --git a/src/components/visual/tooltip/tooltip.tsx b/src/components/visual/tooltip/tooltip.tsx deleted file mode 100644 index dd2d3376..00000000 --- a/src/components/visual/tooltip/tooltip.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type { EventEmitter } from '@stencil/core'; -import { Component, Event, h, Prop, State } from '@stencil/core'; - -// prettier-ignore -const styles = { - tooltip: 'tooltip mvx:flex mvx:relative', -} satisfies Record; - -@Component({ - tag: 'mvx-tooltip', - styleUrl: 'tooltip.scss', - shadow: true, -}) -export class Tooltip { - @Event() triggerRender: EventEmitter; - @State() isTooltipVisible: boolean = false; - - @Prop() position: 'top' | 'bottom' = 'top'; - @Prop() triggerOnClick?: boolean = false; - @Prop() trigger: HTMLElement; - @Prop() class?: string; - - constructor() { - this.handleEllipsisClick = this.handleEllipsisClick.bind(this); - this.handleFocusOut = this.handleFocusOut.bind(this); - } - - private setTooltipVisible(isTooltipVisible: boolean) { - this.isTooltipVisible = isTooltipVisible; - this.triggerRender.emit(this.isTooltipVisible); - } - - private handleEllipsisClick(event: MouseEvent) { - if (!this.triggerOnClick) { - return; - } - - event.preventDefault(); - this.setTooltipVisible(!this.isTooltipVisible); - } - - private handleFocusOut(event: FocusEvent) { - const relatedTarget = event.relatedTarget as Node; - const currentTarget = event.currentTarget as HTMLElement; - - if (!currentTarget.contains(relatedTarget)) { - this.setTooltipVisible(false); - } - } - - private handleMouseEvent(isTooltipVisible: boolean) { - if (this.triggerOnClick) { - return; - } - - return (event: MouseEvent) => { - event.preventDefault(); - this.setTooltipVisible(isTooltipVisible); - }; - } - - render() { - return ( -
- {this.isTooltipVisible && ( -
-
event.stopPropagation()} - > - -
-
- )} - - {this.trigger} -
- ); - } -} diff --git a/src/global/tailwind.css b/src/global/tailwind.css index 35760edb..d0822f3e 100644 --- a/src/global/tailwind.css +++ b/src/global/tailwind.css @@ -13,6 +13,7 @@ --color-preloader: var(--mvx-preloader-item-bg); --color-pending: var(--mvx-pending-color); --color-secondary: var(--mvx-bg-color-secondary); + --color-tertiary: var(--mvx-bg-color-tertiary); --color-error: var(--mvx-error-color); --color-primary: var(--mvx-text-color-primary); --color-secondary-text: var(--mvx-text-color-secondary); @@ -21,6 +22,7 @@ --color-hover: var(--mvx-hover-color-primary); --color-pagination-item: var(--mvx-pagination-item-bg); --color-pagination-item-hover: var(--mvx-pagination-item-bg-hover); + --color-neutral-700: var(--mvx-neutral-700); --color-neutral-950: var(--mvx-neutral-950); --color-neutral-900: var(--mvx-neutral-900); --color-ledger-warning: var(--mvx-ledger-warning-bg); @@ -29,9 +31,15 @@ --color-transaction-method: var(--mvx-transaction-method); --color-slate-550: var(--mvx-slate-550); --color-gray-200: var(--mvx-gray-200); + --color-neutral-925: var(--mvx-neutral-925); --color-thumb: var(--mvx-scrollbar-thumb); --color-outline-variant: var(--mvx-border-color-secondary); --color-emphasize: var(--mvx-bg-accent-color); + --color-accent-variant: var(--mvx-bg-accent-variant-color); + --color-button-primary: var(--mvx-button-text-primary); + --color-button-secondary: var(--mvx-button-text-secondary); + --color-button-bg-primary: var(--mvx-button-bg-primary); + --color-button-bg-secondary: var(--mvx-button-bg-secondary); } @layer utilities { @@ -83,4 +91,14 @@ letter-spacing: -0.001em; } } + + @keyframes SpinnerAnimation { + to { + transform: rotate(360deg); + } + } + + .mvx\:animate-spinner { + animation: SpinnerAnimation 3000ms linear infinite; + } } \ No newline at end of file