diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec4967c..b0630bce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [Remove pagination webcomponent](https://github.com/multiversx/mx-sdk-dapp-ui/pull/282) + + ## [[0.1.7](https://github.com/multiversx/mx-sdk-dapp-ui/pull/281)] - 2025-12-11 - [Fixed compatibility issue on stencil/core](https://github.com/multiversx/mx-sdk-dapp-ui/pull/279) ## [[0.1.6](https://github.com/multiversx/mx-sdk-dapp-ui/pull/277)] - 2025-12-09 -- [Fixed compatibility issue on stencil/core](https://github.com/multiversx/mx-sdk-dapp-ui/pull/279) - [Sync development with main](https://github.com/multiversx/mx-sdk-dapp-ui/pull/278) - [Fixed copy button](https://github.com/multiversx/mx-sdk-dapp-ui/pull/276) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..96b3d7e0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +packages: + - . + +onlyBuiltDependencies: + - postcss-combine-duplicated-selectors + + diff --git a/src/common/ProviderIdleScreen/ProviderIdleScreen.tsx b/src/common/ProviderIdleScreen/ProviderIdleScreen.tsx index 93cbab78..4e0347f6 100644 --- a/src/common/ProviderIdleScreen/ProviderIdleScreen.tsx +++ b/src/common/ProviderIdleScreen/ProviderIdleScreen.tsx @@ -4,7 +4,7 @@ import type { IProviderBase } from 'types/provider.types'; import { ProviderTypeEnum } from 'types/provider.types'; import { getProviderIntroText } from './helpers/getProviderIntroText'; -import { SidePanelHeader } from 'components/visual/SidePanel/components/SidePanelHeader/SidePanelHeader'; +import { SidePanelHeader } from 'common/SidePanel/components/SidePanelHeader/SidePanelHeader'; const styles = { container: 'mvx:flex mvx:flex-col mvx:flex-1 mvx:overflow-hidden', diff --git a/src/components/visual/SidePanel/SidePanel.tsx b/src/common/SidePanel/SidePanel.tsx similarity index 100% rename from src/components/visual/SidePanel/SidePanel.tsx rename to src/common/SidePanel/SidePanel.tsx diff --git a/src/components/visual/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx b/src/common/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx similarity index 85% rename from src/components/visual/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx rename to src/common/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx index 02d55ce4..1cfb2495 100644 --- a/src/components/visual/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx +++ b/src/common/SidePanel/components/SidePanelHeader/SidePanelHeader.tsx @@ -23,16 +23,25 @@ interface SidePanelHeaderPropsType { rightIcon?: any; } -export function SidePanelHeader({ panelClassName, panelTitle, hasLeftButton = true, hasRightButton = true, onRightButtonClick, onLeftButtonClick, leftIcon, rightIcon }: SidePanelHeaderPropsType) { +export function SidePanelHeader({ + panelClassName, + panelTitle, + hasLeftButton = true, + hasRightButton = true, + onRightButtonClick, + onLeftButtonClick, + leftIcon, + rightIcon, +}: SidePanelHeaderPropsType) { const handleRightIconClick = (event: MouseEvent) => { event.preventDefault(); onRightButtonClick?.(event); - } + }; const handleLeftIconClick = (event: MouseEvent) => { event.preventDefault(); onLeftButtonClick?.(event); - } + }; return (
@@ -40,9 +49,7 @@ export function SidePanelHeader({ panelClassName, panelTitle, hasLeftButton = tr class={{ [styles.sidePanelHeadingLeft]: true, [styles.sidePanelHeadingLeftVisible]: hasLeftButton }} onClick={handleLeftIconClick} > - {hasLeftButton && ( - leftIcon || - )} + {hasLeftButton && (leftIcon || )}
{panelTitle}
@@ -51,9 +58,8 @@ export function SidePanelHeader({ panelClassName, panelTitle, hasLeftButton = tr class={{ [styles.sidePanelHeadingRight]: true, [styles.sidePanelHeadingRightVisible]: hasRightButton }} onClick={handleRightIconClick} > - {rightIcon || } + {hasRightButton && (rightIcon || )} - + ); } - diff --git a/src/components/visual/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx b/src/common/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx similarity index 90% rename from src/components/visual/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx rename to src/common/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx index 6589e8a0..2c951b7d 100644 --- a/src/components/visual/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx +++ b/src/common/SidePanel/components/SidePanelSwiper/SidePanelSwiper.tsx @@ -1,4 +1,5 @@ import { h } from '@stencil/core'; + import { state } from '../../sidePanelStore'; import styles from './sidePanelSwiper.styles'; @@ -14,7 +15,7 @@ let previousOpen: boolean | null = null; const snapPointsArray: string[] = ['100%']; const SNAP_PERCENT_DEFAULT = '50'; const OPEN_TIMEOUT_VALUE = 50; -const CLOSE_TTMEOUT_VALUE = 300; +const CLOSE_TIMEOUT_VALUE = 300; const TRANSLATE_Y_VALUE = 100; let sheetElement: HTMLElement | null = null; @@ -27,10 +28,13 @@ let dragState = { let isDragging = false; -export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChange }: SidePanelSwiperPropsType, children: JSX.Element) { +export function SidePanelSwiper( + { open = false, onSheetDismiss, onSheetSnapChange }: SidePanelSwiperPropsType, + children: JSX.Element, +) { const handleSheetDismiss = () => { onSheetDismiss?.(); - } + }; const animateToPosition = (snapIndex: number, emitEvent: boolean = true) => { if (!sheetElement || dragState.isAnimating) { @@ -55,7 +59,7 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang sheetElement.style.transition = ''; } }, 350); - } + }; const openToSnapPoint = (snapIndex: number = 1) => { if (dragState.isAnimating) { @@ -70,7 +74,7 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang animateToPosition(state.currentSnapIndex, false); } }, OPEN_TIMEOUT_VALUE); - } + }; const animateToClose = () => { if (!sheetElement || dragState.isAnimating) { @@ -88,8 +92,8 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang if (sheetElement) { sheetElement.style.transition = ''; } - }, CLOSE_TTMEOUT_VALUE); - } + }, CLOSE_TIMEOUT_VALUE); + }; const closeSwiper = () => { if (dragState.isAnimating || !state.isVisible) { @@ -97,7 +101,7 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang } animateToClose(); - } + }; if (previousOpen !== null && previousOpen !== open) { if (open && !state.isVisible) { @@ -124,8 +128,7 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang openToSnapPoint(state.currentSnapIndex); } } - } - + }; const handleDragStart = (e: MouseEvent | TouchEvent) => { if (dragState.isAnimating) { @@ -227,15 +230,17 @@ export function SidePanelSwiper({ open = false, onSheetDismiss, onSheetSnapChang } } return TRANSLATE_Y_VALUE; - } + }; return (
-
-
event.stopPropagation()} - > +
+
event.stopPropagation()}>
-
- {children} -
+
{children}
); } - diff --git a/src/components/visual/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts b/src/common/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts similarity index 63% rename from src/components/visual/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts rename to src/common/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts index 4affe250..9e900819 100644 --- a/src/components/visual/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts +++ b/src/common/SidePanel/components/SidePanelSwiper/sidePanelSwiper.styles.ts @@ -1,7 +1,7 @@ // prettier-ignore export default { - sidePanelSwiperContainer: 'side-panel-swipper-container mvx:flex mvx:xs:flex-col mvx:xs:h-full', - sidePanelSwiperWrapper: 'side-panel-swipper-wrapper mvx:fixed mvx:left-0 mvx:top-0 mvx:bottom-0 mvx:right-0 mvx:z-50 mvx:xs:static mvx:xs:h-full mvx:before:opacity-90 mvx:before:left-0 mvx:before:top-0 mvx:before:right-0 mvx:before:bottom-0 mvx:before:transition-all mvx:before:duration-200 mvx:before:pointer-events-none mvx:before:absolute mvx:before:ease-in-out mvx:before:bg-neutral-900 mvx:before:content-[""] mvx:before:supports-[backdrop-filter]:opacity-50 mvx:before:supports-[backdrop-filter]:backdrop-blur-sm mvx:before:supports-[backdrop-filter]:bg-neutral-900 mvx:xs:before:content-none', + sidePanelSwiperContainer: 'side-panel-swiper-container mvx:flex mvx:xs:flex-col mvx:xs:h-full', + sidePanelSwiperWrapper: 'side-panel-swiper-wrapper mvx:fixed mvx:left-0 mvx:top-0 mvx:bottom-0 mvx:right-0 mvx:z-50 mvx:xs:static mvx:xs:h-full mvx:before:opacity-90 mvx:before:left-0 mvx:before:top-0 mvx:before:right-0 mvx:before:bottom-0 mvx:before:transition-all mvx:before:duration-200 mvx:before:pointer-events-none mvx:before:absolute mvx:before:ease-in-out mvx:before:bg-neutral-900 mvx:before:content-[""] mvx:before:supports-[backdrop-filter]:opacity-50 mvx:before:supports-[backdrop-filter]:backdrop-blur-sm mvx:before:supports-[backdrop-filter]:bg-neutral-900 mvx:xs:before:content-none', sidePanelSwiperWrapperVisible: 'side-panel-swiper-visible mvx:!flex', sidePanelSwiperWrapperHidden: 'side-panel-swiper-wrapper-hidden mvx:hidden mvx:xs:block', sidePanelSwiperHidden: 'side-panel-swiper-hidden mvx:translate-y-full', @@ -10,4 +10,4 @@ export default { sidePanelSwiperHandleContainer: 'side-panel-swiper-handle-container mvx:flex mvx:top-0 mvx:bottom-0 mvx:absolute mvx:right-0 mvx:left-0 mvx:justify-center mvx:touch-none mvx:select-none mvx:cursor-grab mvx:active:cursor-grabbing', sidePanelSwiperHandle: 'side-panel-swiper-handle mvx:w-32 mvx:mt-3 mvx:h-1 mvx:rounded mvx:bg-primary', sidePanelSwiperContent: 'side-panel-swiper-content mvx:overflow-y-auto mvx:max-h-[calc(100dvh-4rem)] mvx:xs:max-h-none mvx:xs:h-full' -} satisfies Record; \ No newline at end of file +} satisfies Record; diff --git a/src/components/visual/SidePanel/helpers/handleSidePanelOpenChange.ts b/src/common/SidePanel/helpers/handleSidePanelOpenChange.ts similarity index 100% rename from src/components/visual/SidePanel/helpers/handleSidePanelOpenChange.ts rename to src/common/SidePanel/helpers/handleSidePanelOpenChange.ts diff --git a/src/common/SidePanel/sidePanel.constants.ts b/src/common/SidePanel/sidePanel.constants.ts new file mode 100644 index 00000000..cfe6bd5a --- /dev/null +++ b/src/common/SidePanel/sidePanel.constants.ts @@ -0,0 +1 @@ +export const ANIMATION_DELAY_PROMISE = () => new Promise(resolve => setTimeout(resolve, 300)); diff --git a/src/components/visual/SidePanel/sidePanel.styles.ts b/src/common/SidePanel/sidePanel.styles.ts similarity index 100% rename from src/components/visual/SidePanel/sidePanel.styles.ts rename to src/common/SidePanel/sidePanel.styles.ts diff --git a/src/components/visual/SidePanel/sidePanelStore.ts b/src/common/SidePanel/sidePanelStore.ts similarity index 100% rename from src/components/visual/SidePanel/sidePanelStore.ts rename to src/common/SidePanel/sidePanelStore.ts diff --git a/src/common/Tooltip/Tooltip.tsx b/src/common/Tooltip/Tooltip.tsx index a82813a4..0df67c39 100644 --- a/src/common/Tooltip/Tooltip.tsx +++ b/src/common/Tooltip/Tooltip.tsx @@ -91,7 +91,6 @@ export function Tooltip(
)} - {trigger}
); diff --git a/src/common/TransactionAssetIcon/TransactionAssetIcon.tsx b/src/common/TransactionAssetIcon/TransactionAssetIcon.tsx index 7ca25685..c0541cae 100644 --- a/src/common/TransactionAssetIcon/TransactionAssetIcon.tsx +++ b/src/common/TransactionAssetIcon/TransactionAssetIcon.tsx @@ -1,7 +1,7 @@ import { h } from '@stencil/core'; import classNames from 'classnames'; import { Icon } from 'common/Icon'; -import type { ITransactionListItem } from 'components/visual/TransactionListItem/transactionListItem.types'; +import type { ITransactionListItem } from 'components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types'; export enum IconSizeEnumType { small = 'small', @@ -16,7 +16,13 @@ interface ITransactionAssetIconProps { textClass?: string; } -export function TransactionAssetIcon({ transaction, iconSize, iconClass, imgClass, textClass }: ITransactionAssetIconProps) { +export function TransactionAssetIcon({ + transaction, + iconSize, + iconClass, + imgClass, + textClass, +}: ITransactionAssetIconProps) { if (transaction?.asset === null) { return iconSize === IconSizeEnumType.small ? ( @@ -31,7 +37,7 @@ export function TransactionAssetIcon({ transaction, iconSize, iconClass, imgClas src={transaction.asset.imageUrl} alt="Transaction icon" loading="lazy" - class={classNames("mvx:max-w-full mvx:max-h-full", imgClass)} + class={classNames('mvx:max-w-full mvx:max-h-full', imgClass)} /> ); } diff --git a/src/components.d.ts b/src/components.d.ts index 4482b8fd..1e97b47e 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -11,7 +11,7 @@ import { CustomToastType, IComponentToast, ISimpleToast } from "./components/fun import { IConfirmScreenData, IConnectScreenData, ILedgerConnectPanelData } from "./components/functional/ledger-connect/ledger-connect.types"; import { IEventBus } from "./utils/EventBus"; import { LocalJSX as JSX } from "@stencil/core"; -import { ITransactionListItem } from "./components/visual/TransactionListItem/transactionListItem.types"; +import { ITransactionListItem } from "./components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types"; import { IToastDataState, ITransactionProgressState } from "./components/functional/toasts-list/components/transaction-toast/transaction-toast.type"; import { TransactionStatusEnum } from "./constants/transactionStatus.enum"; import { TransactionRowType } from "./components/controlled/transactions-table/transactions-table.type"; @@ -22,7 +22,7 @@ export { CustomToastType, IComponentToast, ISimpleToast } from "./components/fun export { IConfirmScreenData, IConnectScreenData, ILedgerConnectPanelData } from "./components/functional/ledger-connect/ledger-connect.types"; export { IEventBus } from "./utils/EventBus"; export { LocalJSX as JSX } from "@stencil/core"; -export { ITransactionListItem } from "./components/visual/TransactionListItem/transactionListItem.types"; +export { ITransactionListItem } from "./components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types"; export { IToastDataState, ITransactionProgressState } from "./components/functional/toasts-list/components/transaction-toast/transaction-toast.type"; export { TransactionStatusEnum } from "./constants/transactionStatus.enum"; export { TransactionRowType } from "./components/controlled/transactions-table/transactions-table.type"; @@ -179,31 +179,6 @@ export namespace Components { "closeWithAnimation": () => Promise; "getEventBus": () => Promise; } - interface MvxPagination { - "class"?: string; - /** - * @default 1 - */ - "currentPage": number; - /** - * @default false - */ - "isDisabled"?: boolean; - "totalPages": number; - } - interface MvxPaginationEllipsis { - /** - * @default false - */ - "isActive": boolean; - } - interface MvxPaginationEllipsisForm { - /** - * @default false - */ - "isVisible": boolean; - "maxPageToSearchFor": number; - } interface MvxPasskeyProviderIcon { "class"?: string; } @@ -371,14 +346,6 @@ export interface MvxLedgerIntroCustomEvent extends CustomEvent { detail: T; target: HTMLMvxLedgerIntroElement; } -export interface MvxPaginationCustomEvent extends CustomEvent { - detail: T; - target: HTMLMvxPaginationElement; -} -export interface MvxPaginationEllipsisFormCustomEvent extends CustomEvent { - detail: T; - target: HTMLMvxPaginationEllipsisFormElement; -} export interface MvxSimpleToastCustomEvent extends CustomEvent { detail: T; target: HTMLMvxSimpleToastElement; @@ -619,46 +586,6 @@ declare global { prototype: HTMLMvxNotificationsFeedElement; new (): HTMLMvxNotificationsFeedElement; }; - interface HTMLMvxPaginationElementEventMap { - "pageChange": number; - } - interface HTMLMvxPaginationElement extends Components.MvxPagination, HTMLStencilElement { - addEventListener(type: K, listener: (this: HTMLMvxPaginationElement, ev: MvxPaginationCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLMvxPaginationElement, ev: MvxPaginationCustomEvent) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; - } - var HTMLMvxPaginationElement: { - prototype: HTMLMvxPaginationElement; - new (): HTMLMvxPaginationElement; - }; - interface HTMLMvxPaginationEllipsisElement extends Components.MvxPaginationEllipsis, HTMLStencilElement { - } - var HTMLMvxPaginationEllipsisElement: { - prototype: HTMLMvxPaginationEllipsisElement; - new (): HTMLMvxPaginationEllipsisElement; - }; - interface HTMLMvxPaginationEllipsisFormElementEventMap { - "search": number; - } - interface HTMLMvxPaginationEllipsisFormElement extends Components.MvxPaginationEllipsisForm, HTMLStencilElement { - addEventListener(type: K, listener: (this: HTMLMvxPaginationEllipsisFormElement, ev: MvxPaginationEllipsisFormCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLMvxPaginationEllipsisFormElement, ev: MvxPaginationEllipsisFormCustomEvent) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; - } - var HTMLMvxPaginationEllipsisFormElement: { - prototype: HTMLMvxPaginationEllipsisFormElement; - new (): HTMLMvxPaginationEllipsisFormElement; - }; interface HTMLMvxPasskeyProviderIconElement extends Components.MvxPasskeyProviderIcon, HTMLStencilElement { } var HTMLMvxPasskeyProviderIconElement: { @@ -892,9 +819,6 @@ declare global { "mvx-multiversx-logo-icon": HTMLMvxMultiversxLogoIconElement; "mvx-multiversx-symbol-icon": HTMLMvxMultiversxSymbolIconElement; "mvx-notifications-feed": HTMLMvxNotificationsFeedElement; - "mvx-pagination": HTMLMvxPaginationElement; - "mvx-pagination-ellipsis": HTMLMvxPaginationEllipsisElement; - "mvx-pagination-ellipsis-form": HTMLMvxPaginationEllipsisFormElement; "mvx-passkey-provider-icon": HTMLMvxPasskeyProviderIconElement; "mvx-pending-transactions-panel": HTMLMvxPendingTransactionsPanelElement; "mvx-preloader": HTMLMvxPreloaderElement; @@ -1078,33 +1002,6 @@ declare namespace LocalJSX { } interface MvxNotificationsFeed { } - interface MvxPagination { - "class"?: string; - /** - * @default 1 - */ - "currentPage"?: number; - /** - * @default false - */ - "isDisabled"?: boolean; - "onPageChange"?: (event: MvxPaginationCustomEvent) => void; - "totalPages"?: number; - } - interface MvxPaginationEllipsis { - /** - * @default false - */ - "isActive"?: boolean; - } - interface MvxPaginationEllipsisForm { - /** - * @default false - */ - "isVisible"?: boolean; - "maxPageToSearchFor"?: number; - "onSearch"?: (event: MvxPaginationEllipsisFormCustomEvent) => void; - } interface MvxPasskeyProviderIcon { "class"?: string; } @@ -1276,9 +1173,6 @@ declare namespace LocalJSX { "mvx-multiversx-logo-icon": MvxMultiversxLogoIcon; "mvx-multiversx-symbol-icon": MvxMultiversxSymbolIcon; "mvx-notifications-feed": MvxNotificationsFeed; - "mvx-pagination": MvxPagination; - "mvx-pagination-ellipsis": MvxPaginationEllipsis; - "mvx-pagination-ellipsis-form": MvxPaginationEllipsisForm; "mvx-passkey-provider-icon": MvxPasskeyProviderIcon; "mvx-pending-transactions-panel": MvxPendingTransactionsPanel; "mvx-preloader": MvxPreloader; @@ -1337,9 +1231,6 @@ declare module "@stencil/core" { "mvx-multiversx-logo-icon": LocalJSX.MvxMultiversxLogoIcon & JSXBase.HTMLAttributes; "mvx-multiversx-symbol-icon": LocalJSX.MvxMultiversxSymbolIcon & JSXBase.HTMLAttributes; "mvx-notifications-feed": LocalJSX.MvxNotificationsFeed & JSXBase.HTMLAttributes; - "mvx-pagination": LocalJSX.MvxPagination & JSXBase.HTMLAttributes; - "mvx-pagination-ellipsis": LocalJSX.MvxPaginationEllipsis & JSXBase.HTMLAttributes; - "mvx-pagination-ellipsis-form": LocalJSX.MvxPaginationEllipsisForm & JSXBase.HTMLAttributes; "mvx-passkey-provider-icon": LocalJSX.MvxPasskeyProviderIcon & JSXBase.HTMLAttributes; "mvx-pending-transactions-panel": LocalJSX.MvxPendingTransactionsPanel & JSXBase.HTMLAttributes; "mvx-preloader": LocalJSX.MvxPreloader & JSXBase.HTMLAttributes; diff --git a/src/components/functional/ledger-connect/ledger-connect.tsx b/src/components/functional/ledger-connect/ledger-connect.tsx index a35c67fd..fb53478b 100644 --- a/src/components/functional/ledger-connect/ledger-connect.tsx +++ b/src/components/functional/ledger-connect/ledger-connect.tsx @@ -1,4 +1,5 @@ import { Component, Element, h, Host, Method, Prop, State, Watch } from '@stencil/core'; +import { SidePanelHeader } from 'common/SidePanel/components/SidePanelHeader/SidePanelHeader'; import { providerLabels } from 'constants/providerFactory.constants'; import { ConnectionMonitor } from 'utils/ConnectionMonitor'; import { EventBus, type IEventBus } from 'utils/EventBus'; @@ -6,7 +7,6 @@ import { EventBus, type IEventBus } from 'utils/EventBus'; import { getLedgerAddressByIndex } from './helpers/getLedgerAddressByIndex'; import type { ILedgerConnectPanelData } from './ledger-connect.types'; import { LedgerConnectEventsEnum } from './ledger-connect.types'; -import { SidePanelHeader } from 'components/visual/SidePanel/components/SidePanelHeader/SidePanelHeader'; // prettier-ignore const styles = { diff --git a/src/components/visual/TransactionListItem/TransactionListItem.tsx b/src/components/functional/notifications-feed/components/TransactionListItem/TransactionListItem.tsx similarity index 84% rename from src/components/visual/TransactionListItem/TransactionListItem.tsx rename to src/components/functional/notifications-feed/components/TransactionListItem/TransactionListItem.tsx index 7d72281e..379d958c 100644 --- a/src/components/visual/TransactionListItem/TransactionListItem.tsx +++ b/src/components/functional/notifications-feed/components/TransactionListItem/TransactionListItem.tsx @@ -1,15 +1,15 @@ import { h } from '@stencil/core'; import classNames from 'classnames'; import { FormatAmount } from 'common/FormatAmount/FormatAmount'; +import { Icon } from 'common/Icon'; +import { Tooltip } from 'common/Tooltip/Tooltip'; import { IconSizeEnumType, TransactionAssetIcon } from 'common/TransactionAssetIcon/TransactionAssetIcon'; import { Trim } from 'common/Trim/Trim'; -import { getAmountParts } from 'components/functional/toasts-list/helpers'; +import { getAmountParts } from 'components/functional/toasts-list/helpers/getAmountParts'; import { getIsTransactionFailed } from 'utils/getTransactionStatus'; -import styles from './transactionListItem.styles' +import styles from './transactionListItem.styles'; import type { ITransactionListItem } from './transactionListItem.types'; -import { Tooltip } from 'common/Tooltip/Tooltip'; -import { Icon } from 'common/Icon'; interface TransactionListItemPropsType { transaction: ITransactionListItem; @@ -23,7 +23,7 @@ export function TransactionListItem({ transaction }: TransactionListItemPropsTyp const showTooltip = transaction.amount && amount.label.length > 12; return ( - +

- {transaction.action.name} + {transaction.action?.name || 'Unknown Action'}

{showTooltip && ( - }> + }> {amount.label} )} @@ -76,9 +76,14 @@ export function TransactionListItem({ transaction }: TransactionListItemPropsTyp
{transaction.interactorAsset ? ( - Service icon + Service icon ) : ( - + )}
@@ -89,4 +94,3 @@ export function TransactionListItem({ transaction }: TransactionListItemPropsTyp
); } - diff --git a/src/components/visual/TransactionListItem/tests/transaction-list-item.spec.tsx b/src/components/functional/notifications-feed/components/TransactionListItem/tests/TransactionListItem.spec.tsx similarity index 98% rename from src/components/visual/TransactionListItem/tests/transaction-list-item.spec.tsx rename to src/components/functional/notifications-feed/components/TransactionListItem/tests/TransactionListItem.spec.tsx index 6b9d4f38..e589ba3b 100644 --- a/src/components/visual/TransactionListItem/tests/transaction-list-item.spec.tsx +++ b/src/components/functional/notifications-feed/components/TransactionListItem/tests/TransactionListItem.spec.tsx @@ -8,7 +8,7 @@ import type { ITransactionListItem } from '../transactionListItem.types'; describe('transaction-list-item', () => { const createPage = async (transaction: ITransactionListItem) => { const page = await newSpecPage({ - components: [], + components: [TransactionListItem], template: () => , }); @@ -38,7 +38,7 @@ describe('transaction-list-item', () => { describe('empty state', () => { it('renders empty when no transaction is provided', async () => { const page = await newSpecPage({ - components: [], + components: [TransactionListItem], template: () => , }); expect(page.root).toBeFalsy(); @@ -156,4 +156,4 @@ describe('transaction-list-item', () => { expect(amount.textContent.trim()).toBe(transaction.amount.replace(' ', '')); }); }); -}); \ No newline at end of file +}); diff --git a/src/components/visual/TransactionListItem/transactionListItem.styles.ts b/src/components/functional/notifications-feed/components/TransactionListItem/transactionListItem.styles.ts similarity index 100% rename from src/components/visual/TransactionListItem/transactionListItem.styles.ts rename to src/components/functional/notifications-feed/components/TransactionListItem/transactionListItem.styles.ts diff --git a/src/components/visual/TransactionListItem/transactionListItem.types.ts b/src/components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types.ts similarity index 100% rename from src/components/visual/TransactionListItem/transactionListItem.types.ts rename to src/components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types.ts diff --git a/src/components/functional/notifications-feed/notifications-feed.tsx b/src/components/functional/notifications-feed/notifications-feed.tsx index ce4a8cc0..409f220c 100644 --- a/src/components/functional/notifications-feed/notifications-feed.tsx +++ b/src/components/functional/notifications-feed/notifications-feed.tsx @@ -1,15 +1,15 @@ import { Component, h, Method, State } from '@stencil/core'; import { Icon } from 'common/Icon'; -import { SidePanel } from 'components/visual/SidePanel/SidePanel'; +import { SidePanel } from 'common/SidePanel/SidePanel'; +import { ANIMATION_DELAY_PROMISE } from 'common/SidePanel/sidePanel.constants'; +import { TransactionListItem } from 'components/functional/notifications-feed/components/TransactionListItem/TransactionListItem'; +import type { ITransactionListItem } from 'components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types'; import { ConnectionMonitor } from 'utils/ConnectionMonitor'; import type { IEventBus } from 'utils/EventBus'; import { EventBus } from 'utils/EventBus'; -import { ITransactionListItem } from 'components/visual/TransactionListItem/transactionListItem.types'; import type { ITransactionToast } from '../toasts-list/components/transaction-toast/transaction-toast.type'; import { NotificationsFeedEventsEnum } from './notifications-feed.types'; -import { ANIMATION_DELAY_PROMISE } from 'components/visual/SidePanel/sidePanel.constants'; -import { TransactionListItem } from 'components/visual/TransactionListItem/TransactionListItem'; @Component({ tag: 'mvx-notifications-feed', @@ -29,7 +29,7 @@ export class NotificationsFeed { @Method() async closeWithAnimation() { this.isOpen = false; - const animationDelay = await ANIMATION_DELAY_PROMISE; + const animationDelay = await ANIMATION_DELAY_PROMISE(); return animationDelay; } @@ -94,12 +94,7 @@ export class NotificationsFeed { const hasPending = this.pendingTransactions?.length > 0; return ( - +
This feed is stored in your browser and will be reset when a new session is started. @@ -109,9 +104,7 @@ export class NotificationsFeed { {hasPending && (
Processing...
- {this.pendingTransactions?.map(toast => ( - - ))} + {this.pendingTransactions?.map(toast => )}
)} diff --git a/src/components/functional/notifications-feed/tests/notifications-feed.spec.tsx b/src/components/functional/notifications-feed/tests/notifications-feed.spec.tsx index 0e346314..aeb73a74 100644 --- a/src/components/functional/notifications-feed/tests/notifications-feed.spec.tsx +++ b/src/components/functional/notifications-feed/tests/notifications-feed.spec.tsx @@ -2,7 +2,7 @@ import { newSpecPage } from '@stencil/core/testing'; import { NotificationsFeed } from '../notifications-feed'; import { NotificationsFeedEventsEnum } from '../notifications-feed.types'; -import type { ITransactionListItem } from 'components/visual/TransactionListItem/transactionListItem.types'; +import type { ITransactionListItem } from 'components/functional/notifications-feed/components/TransactionListItem/transactionListItem.types'; describe('notifications-feed', () => { const mockPendingTransactions = [ diff --git a/src/components/functional/pending-transactions-panel/pending-transactions-panel.tsx b/src/components/functional/pending-transactions-panel/pending-transactions-panel.tsx index 5b34d5ce..16683643 100644 --- a/src/components/functional/pending-transactions-panel/pending-transactions-panel.tsx +++ b/src/components/functional/pending-transactions-panel/pending-transactions-panel.tsx @@ -1,7 +1,7 @@ import { Component, h, Method, State } from '@stencil/core'; import { ProviderIdleScreen } from 'common/ProviderIdleScreen/ProviderIdleScreen'; -import { ANIMATION_DELAY_PROMISE } from 'components/visual/SidePanel/sidePanel.constants'; -import { SidePanel } from 'components/visual/SidePanel/SidePanel'; +import { SidePanel } from 'common/SidePanel/SidePanel'; +import { ANIMATION_DELAY_PROMISE } from 'common/SidePanel/sidePanel.constants'; import type { IProviderBase } from 'types/provider.types'; import { ProviderTypeEnum } from 'types/provider.types'; import { ConnectionMonitor } from 'utils/ConnectionMonitor'; @@ -45,7 +45,7 @@ export class PendingTransactionsPanel { @Method() async closeWithAnimation() { this.isOpen = false; - const animationDelay = await ANIMATION_DELAY_PROMISE; + const animationDelay = await ANIMATION_DELAY_PROMISE(); return animationDelay; } @@ -77,11 +77,7 @@ export class PendingTransactionsPanel { render() { return ( - + +
setTimeout(resolve, 300)); diff --git a/src/components/visual/address-table/address-table.tsx b/src/components/visual/address-table/address-table.tsx index 18ccd236..b89efe13 100644 --- a/src/components/visual/address-table/address-table.tsx +++ b/src/components/visual/address-table/address-table.tsx @@ -1,9 +1,11 @@ import type { EventEmitter } from '@stencil/core'; -import { Component, Event, h, Prop } from '@stencil/core'; +import { Component, Event, h, Prop, State } from '@stencil/core'; import classNames from 'classnames'; import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; import type { IAddressTableData, IndexedAccountType } from 'types/address-table.types'; +import { Pagination } from './components/pagination/Pagination'; + const TOTAL_ADDRESSES_COUNT = 5000; const addressTableClasses: Record = { button: 'mvx:w-full', @@ -22,6 +24,9 @@ export class AddressTable { @Event({ bubbles: false, composed: false }) selectAccount: EventEmitter; @Event() pageChange: EventEmitter; + @State() activeTooltipIndex: number | null = null; + @State() isTooltipOpen: boolean = false; + handleAccessWallet(event: MouseEvent) { event.preventDefault(); this.accessWallet.emit(); @@ -34,16 +39,15 @@ export class AddressTable { }; } - private handlePageChange(event: CustomEvent) { - event.preventDefault(); - event.stopPropagation(); - this.pageChange.emit(event.detail); - } - private processLedgerAddressIndex(accountDerivation: IndexedAccountType) { return Number(accountDerivation.index + 1).toLocaleString(); } + private handlePaginationTooltipStatusChange = (index: number | null, isOpen: boolean) => { + this.activeTooltipIndex = index; + this.isTooltipOpen = isOpen; + }; + render() { const isPageChanging = this.accountScreenData.isLoading; const isAddressesLoadingInitially = this.accountScreenData.accounts.length === 0; @@ -144,12 +148,15 @@ export class AddressTable {
- this.handlePageChange(event)} + onPageChange={(page: number) => this.pageChange.emit(page)} currentPage={Math.floor(this.accountScreenData.startIndex / this.accountScreenData.addressesPerPage) + 1} + activeTooltipIndex={this.activeTooltipIndex} + isTooltipOpen={this.isTooltipOpen} + onTooltipStatusChange={this.handlePaginationTooltipStatusChange} />
diff --git a/src/components/visual/address-table/components/pagination/Pagination.tsx b/src/components/visual/address-table/components/pagination/Pagination.tsx new file mode 100644 index 00000000..62efdd85 --- /dev/null +++ b/src/components/visual/address-table/components/pagination/Pagination.tsx @@ -0,0 +1,171 @@ +import { h } from '@stencil/core'; +import { Icon } from 'common/Icon'; +import { Tooltip } from 'common/Tooltip/Tooltip'; +import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; + +import { PaginationEllipsis } from './components/PaginationEllipsis/PaginationEllipsis'; +import { PaginationEllipsisForm } from './components/PaginationEllipsisForm/PaginationEllipsisForm'; +import { getPagination } from './helpers'; +import styles from './pagination.styles'; + +export interface PaginationPropsType { + currentPage: number; + totalPages: number; + isDisabled?: boolean; + class?: string; + onPageChange?: (page: number) => void; + activeTooltipIndex?: number | null; + isTooltipOpen?: boolean; + onTooltipStatusChange?: (index: number | null, isOpen: boolean) => void; + pageValue?: string; + onPageValueChange?: (value: string) => void; +} + +export function Pagination({ + activeTooltipIndex = null, + isTooltipOpen = false, + onTooltipStatusChange, + currentPage = 1, + totalPages, + isDisabled = false, + class: className, + onPageChange, + pageValue = '', + onPageValueChange, +}: PaginationPropsType) { + const handleTooltipStatus = (index: number | null, isOpen: boolean) => { + onTooltipStatusChange?.(index, isOpen); + }; + + const handlePageClick = (newPageIndex: number) => { + if (newPageIndex === currentPage) { + return; + } + onPageChange?.(newPageIndex); + }; + + const handleEdgePageClick = (pageToNavigateTo: number) => { + return (event: MouseEvent) => { + event.preventDefault(); + handlePageClick(pageToNavigateTo); + }; + }; + + const isCurrentPageActive = (paginationItem: string) => { + return parseFloat(paginationItem) === currentPage; + }; + + const isInTheHundreds = (paginationItem: string) => { + return parseFloat(paginationItem) && parseFloat(paginationItem) >= 100; + }; + + /** + * Generates CSS classes for pagination edge buttons (previous/next) + * @param isInactive - Whether the button should be inactive + * @returns Object with CSS class mappings + */ + + const getPaginationEdgeButtonClasses = (isInactive: boolean) => { + return { + [styles.paginationEdgeButton]: true, + [styles.paginationEdgeButtonDisabled]: isDisabled, + [styles.paginationEdgeButtonInactive]: isInactive, + }; + }; + + /** + * Generates CSS classes for pagination angle buttons (first/last) + * @param isInactive - Whether the button should be inactive/disabled + * @returns Object with CSS class mappings + */ + + const getPaginationAngleClasses = (isInactive: boolean) => { + return { + [styles.paginationAngle]: true, + [styles.paginationAngleDisabled]: isDisabled, + [styles.paginationAngleInactive]: isInactive, + }; + }; + + const isLeftToggleDisabled = currentPage === 1; + const isRightToggleDisabled = currentPage === totalPages; + const paginationItems = getPagination({ currentPage, totalPages }); + + return ( +
+ + + +
+ +
+
+ {paginationItems.map((paginationItem, paginationItemIndex) => ( +
+ {parseFloat(paginationItem) ? ( +
handlePageClick(Number(paginationItem))} + data-testid={`${DataTestIdsEnum.paginationItem}-${paginationItem}`} + class={{ + [styles.paginationItem]: true, + [styles.paginationItemBefore]: true, + [styles.paginationItemActive]: isCurrentPageActive(paginationItem), + [styles.paginationItemHundreds]: isInTheHundreds(paginationItem), + }} + > + {paginationItem} +
+ ) : ( + } + onVisibilityChange={(isVisible: boolean) => { + handleTooltipStatus(paginationItemIndex, isVisible); + }} + > + {!isDisabled && ( + onPageValueChange?.(value)} + onSearch={page => handlePageClick(page)} + /> + )} + + )} +
+ ))} +
+
+ +
+ + + +
+ ); +} diff --git a/src/components/visual/address-table/components/pagination/components/PaginationEllipsis/PaginationEllipsis.tsx b/src/components/visual/address-table/components/pagination/components/PaginationEllipsis/PaginationEllipsis.tsx new file mode 100644 index 00000000..257f2de2 --- /dev/null +++ b/src/components/visual/address-table/components/pagination/components/PaginationEllipsis/PaginationEllipsis.tsx @@ -0,0 +1,18 @@ +import { h } from '@stencil/core'; +import { ELLIPSIS } from 'constants/htmlStrings'; + +// prettier-ignore +const styles = { + paginationEllipsis: 'pagination-ellipsis mvx:w-8 mvx:h-8 mvx:cursor-pointer mvx:flex mvx:items-center mvx:justify-center mvx:transition-all mvx:duration-200 mvx:rounded-full mvx:ease-in-out mvx:text-primary mvx:hover:bg-pagination-item-hover mvx:active:bg-pagination-item-hover', + paginationEllipsisActive: 'pagination-ellipsis-active mvx:active:bg-pagination-item-hover' +} satisfies Record; + +interface PaginationEllipsisPropsType { + isActive?: boolean; +} + +export function PaginationEllipsis({ isActive = false }: PaginationEllipsisPropsType) { + return ( +
{ELLIPSIS}
+ ); +} diff --git a/src/components/visual/address-table/components/pagination/components/PaginationEllipsisForm/PaginationEllipsisForm.tsx b/src/components/visual/address-table/components/pagination/components/PaginationEllipsisForm/PaginationEllipsisForm.tsx new file mode 100644 index 00000000..ba4c9f85 --- /dev/null +++ b/src/components/visual/address-table/components/pagination/components/PaginationEllipsisForm/PaginationEllipsisForm.tsx @@ -0,0 +1,97 @@ +import { h } from '@stencil/core'; +import classNames from 'classnames'; + +// prettier-ignore +const styles = { + paginationEllipsisForm: 'pagination-ellipsis-form mvx:cursor-default mvx:flex mvx:flex-col mvx:text-left', + paginationEllipsisFormFieldLabel: 'pagination-ellipsis-form-field-label mvx:cursor-pointer mvx:mb-2 mvx:text-xs mvx:text-secondary-text', + paginationEllipsisFormField: 'pagination-ellipsis-form-field mvx:relative mvx:gap-2 mvx:flex', + paginationEllipsisFormFieldInput: 'pagination-ellipsis-form-field-input mvx:w-22 mvx:h-11 mvx:p-3 mvx:text-center mvx:transition-all mvx:duration-200 mvx:ease-in-out mvx:pr-10 mvx:m-0 mvx:border mvx:shadow-none mvx:text-base mvx:rounded-lg mvx:border-transparent mvx:appearance-none mvx:outline-none mvx:bg-surface mvx:text-primary mvx:focus:border-accent mvx:no-spinner', + paginationEllipsisFormButton: 'pagination-ellipsis-form-button mvx:group mvx:leading-none mvx:h-8 mvx:right-1 mvx:absolute mvx:cursor-pointer mvx:flex mvx:top-1/2 mvx:text-xs mvx:outline-none mvx:border-none mvx:bg-transparent mvx:transform mvx:-translate-y-1/2 mvx:translate-x-0', + paginationEllipsisFormButtonIconHover: 'pagination-ellipsis-form-button-icon-hover mvx:group-hover:fill-accent', + paginationEllipsisFormButtonIcon: 'pagination-ellipsis-form-button-icon mvx:p-[10px] mvx:hover:!fill-teal-400' +} satisfies Record; + +interface PaginationEllipsisFormPropsType { + maxPageToSearchFor: number; + isVisible?: boolean; + pageValue: string; + onSearch: (page: number) => void; + onPageValueChange: (value: string) => void; +} + +export function PaginationEllipsisForm({ + maxPageToSearchFor, + isVisible = false, + pageValue = '', + onSearch, + onPageValueChange, +}: PaginationEllipsisFormPropsType) { + const handleInputRef = (el: HTMLInputElement | null) => { + if (el && isVisible) { + el.focus(); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.code === 'Enter') { + event.preventDefault(); + handleSubmit(event); + } + + if (['Equal', 'Minus', 'Period', 'KeyE', 'Comma'].includes(event.code)) { + event.preventDefault(); + return; + } + }; + + const handleInput = (event: Event) => { + const input = event.target as HTMLInputElement; + const isBelowMax = parseFloat(input.value) <= maxPageToSearchFor; + + if (isBelowMax) { + onPageValueChange(input.value); + } else { + input.value = pageValue; + } + }; + + const handleSubmit = (event: Event) => { + if (!pageValue) { + return; + } + + event.preventDefault(); + onSearch(parseInt(pageValue === '0' ? '1' : pageValue)); + }; + + return ( +
event.stopPropagation()}> + + +
+ + +
+ +
+
+
+ ); +} diff --git a/src/components/visual/pagination/helpers/getPagination/getPagination.constants.ts b/src/components/visual/address-table/components/pagination/helpers/getPagination/getPagination.constants.ts similarity index 100% rename from src/components/visual/pagination/helpers/getPagination/getPagination.constants.ts rename to src/components/visual/address-table/components/pagination/helpers/getPagination/getPagination.constants.ts diff --git a/src/components/visual/pagination/helpers/getPagination/getPagination.ts b/src/components/visual/address-table/components/pagination/helpers/getPagination/getPagination.ts similarity index 100% rename from src/components/visual/pagination/helpers/getPagination/getPagination.ts rename to src/components/visual/address-table/components/pagination/helpers/getPagination/getPagination.ts diff --git a/src/components/visual/pagination/helpers/getPagination/index.ts b/src/components/visual/address-table/components/pagination/helpers/getPagination/index.ts similarity index 100% rename from src/components/visual/pagination/helpers/getPagination/index.ts rename to src/components/visual/address-table/components/pagination/helpers/getPagination/index.ts diff --git a/src/components/visual/pagination/helpers/getPagination/tests/getPagination.spec.ts b/src/components/visual/address-table/components/pagination/helpers/getPagination/tests/getPagination.spec.ts similarity index 100% rename from src/components/visual/pagination/helpers/getPagination/tests/getPagination.spec.ts rename to src/components/visual/address-table/components/pagination/helpers/getPagination/tests/getPagination.spec.ts diff --git a/src/components/visual/pagination/helpers/index.ts b/src/components/visual/address-table/components/pagination/helpers/index.ts similarity index 100% rename from src/components/visual/pagination/helpers/index.ts rename to src/components/visual/address-table/components/pagination/helpers/index.ts diff --git a/src/components/visual/address-table/components/pagination/pagination-story.tsx b/src/components/visual/address-table/components/pagination/pagination-story.tsx new file mode 100644 index 00000000..20e25708 --- /dev/null +++ b/src/components/visual/address-table/components/pagination/pagination-story.tsx @@ -0,0 +1,64 @@ +import type { EventEmitter } from '@stencil/core'; +import { Component, Event, h, Prop, State, Watch } from '@stencil/core'; + +import { Pagination } from './Pagination'; + +@Component({ + tag: 'mvx-pagination', + styleUrl: 'pagination.scss', + shadow: true, +}) +export class PaginationComponent { + @Prop() currentPage: number = 1; + @Prop() totalPages: number; + @Prop() isDisabled?: boolean = false; + @Prop() class?: string; + + @Event({ bubbles: false, composed: false }) pageChange: EventEmitter; + @State() activeTooltipIndex: number | null = null; + @State() isTooltipOpen: boolean = false; + @State() currentPageIndex: number; + @State() pageValue: string = ''; + + @Watch('currentPage') + watchCurrentPage(newValue: number) { + if (newValue !== this.currentPageIndex) { + this.currentPageIndex = newValue; + } + } + + componentWillLoad() { + this.currentPageIndex = this.currentPage; + } + + private handleTooltipStatus = (index: number | null, isOpen: boolean) => { + this.activeTooltipIndex = index; + this.isTooltipOpen = isOpen; + }; + + private handlePageClick = (newPageIndex: number) => { + this.currentPageIndex = newPageIndex; + this.pageChange.emit(newPageIndex); + }; + + private handlePageValueChange = (value: string) => { + this.pageValue = value; + }; + + render() { + return ( + + ); + } +} diff --git a/src/components/visual/address-table/components/pagination/pagination.scss b/src/components/visual/address-table/components/pagination/pagination.scss new file mode 100644 index 00000000..97212501 --- /dev/null +++ b/src/components/visual/address-table/components/pagination/pagination.scss @@ -0,0 +1,2 @@ +// This is needed to trigger the Stencil Tailwind compilation for inline Tailwind classes. + diff --git a/src/components/visual/address-table/components/pagination/pagination.stories.tsx b/src/components/visual/address-table/components/pagination/pagination.stories.tsx new file mode 100644 index 00000000..0830292c --- /dev/null +++ b/src/components/visual/address-table/components/pagination/pagination.stories.tsx @@ -0,0 +1,71 @@ +import './pagination.scss'; + +import { h } from '@stencil/core'; +import type { StoryObj } from '@stencil/storybook-plugin'; + +// prettier-ignore +const styles = { + paginationStoriesWrapper: 'pagination-stories-wrapper mvx:justify-center mvx:flex mvx:gap-4 mvx:pt-24', +} satisfies Record; + +const storySettings = { + tags: ['autodocs'], + title: 'Components/Pagination', + parameters: { + docs: { + description: { + component: + 'Pagination is not exported as a webcomponent. Make sure to exclude the component from the tsconfig.json file.', + }, + }, + }, + args: { + 'current-page': 1, + 'total-pages': 10, + 'is-disabled': false, + 'class': '', + }, + argTypes: { + 'current-page': { control: { type: 'number', min: 1, max: 10 } }, + 'total-pages': { control: { type: 'number', min: 1 } }, + 'is-disabled': { control: 'boolean' }, + 'class': { control: 'text' }, + }, + decorators: [ + Story => ( +
+ +
+ ), + ], +}; + +type PaginationPropsType = typeof storySettings.args; + +export const Default: StoryObj = { + render: props => { + return ; + }, +}; + +export const Disabled: StoryObj = { + render: () => , +}; + +export const FirstPage: StoryObj = { + render: () => , +}; + +export const LastPage: StoryObj = { + render: () => , +}; + +export const SinglePage: StoryObj = { + render: () => , +}; + +export const ManyPages: StoryObj = { + render: () => , +}; + +export default storySettings; diff --git a/src/components/visual/pagination/pagination.styles.ts b/src/components/visual/address-table/components/pagination/pagination.styles.ts similarity index 100% rename from src/components/visual/pagination/pagination.styles.ts rename to src/components/visual/address-table/components/pagination/pagination.styles.ts diff --git a/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.scss b/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.scss deleted file mode 100644 index e1f756a9..00000000 --- a/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.scss +++ /dev/null @@ -1,38 +0,0 @@ -.pagination-ellipsis-form { - @apply mvx:cursor-default mvx:flex mvx:flex-col mvx:text-left; - - .pagination-ellipsis-form-field-label { - @apply mvx:cursor-pointer mvx:mb-2 mvx:text-xs; - color: var(--mvx-text-color-secondary); - } - - .pagination-ellipsis-form-field { - @apply mvx:relative mvx:gap-2 mvx:flex; - - .pagination-ellipsis-form-field-input { - @apply mvx:w-22 mvx:h-11 mvx:p-3 mvx:text-center mvx:transition-all mvx:duration-200 mvx:ease-in-out mvx:pr-10 mvx:m-0; - @apply mvx:border mvx:shadow-none mvx:text-base mvx:rounded-lg mvx:border-transparent mvx:appearance-none mvx:outline-none; - background: var(--mvx-bg-color-primary); - color: var(--mvx-text-color-primary); - border-width: 1px; - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - @apply mvx:appearance-none mvx:m-0; - } - - &:focus { - border-color: var(--mvx-text-accent-color); - } - } - - .pagination-ellipsis-form-button { - @apply mvx:leading-none mvx:h-8 mvx:right-1 mvx:absolute mvx:cursor-pointer mvx:flex mvx:top-1/2 mvx:text-xs; - @apply mvx:outline-none mvx:border-none mvx:bg-transparent mvx:transform mvx:-translate-y-1/2 mvx:translate-x-0; - - &:hover .pagination-ellipsis-form-button-icon { - fill: var(--mvx-text-accent-color); - } - } - } -} diff --git a/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.tsx b/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.tsx deleted file mode 100644 index 179ade4b..00000000 --- a/src/components/visual/pagination/components/pagination-ellipsis-form/pagination-ellipsis-form.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import type { EventEmitter } from '@stencil/core'; -import { Component, Event, h, Prop, State, Watch } from '@stencil/core'; -import classNames from 'classnames'; - -const paginationEllipsisFormClasses: Record = { - buttonIcon: 'mvx:p-[10px] mvx:hover:!fill-teal-400', -}; - -@Component({ - tag: 'mvx-pagination-ellipsis-form', - styleUrl: 'pagination-ellipsis-form.scss', - shadow: true, -}) -export class PaginationEllipsisForm { - @State() pageValue: string = ''; - @Event({ bubbles: false, composed: false }) search: EventEmitter; - - @Prop() maxPageToSearchFor: number; - @Prop() isVisible: boolean = false; - - constructor() { - this.handleInput = this.handleInput.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleInputReference = this.handleInputReference.bind(this); - } - - private inputElement!: HTMLInputElement; - private handleInputReference(inputElement: HTMLInputElement) { - this.inputElement = inputElement; - } - - private handleKeyDown(event: KeyboardEvent) { - if (event.code === 'Enter') { - event.preventDefault(); - this.handleSubmit(event); - } - - if (['Equal', 'Minus', 'Period', 'KeyE', 'Comma'].includes(event.code)) { - event.preventDefault(); - return; - } - } - - private handleInput(event: Event) { - const input = event.target as HTMLInputElement; - const isBelowMax = parseFloat(input.value) <= this.maxPageToSearchFor; - - if (isBelowMax) { - this.pageValue = input.value; - } else { - input.value = this.pageValue; - } - } - - private handleSubmit(event: Event) { - if (!this.pageValue) { - return; - } - - event.preventDefault(); - this.search.emit(parseInt(this.pageValue === '0' ? '1' : this.pageValue)); - } - - @Watch('isVisible') - handleVisibilityChange(newValue: boolean) { - if (newValue && this.inputElement) { - this.inputElement.focus(); - } - } - - render() { - return ( -
event.stopPropagation()}> - - -
- - -
- -
-
-
- ); - } -} diff --git a/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.scss b/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.scss deleted file mode 100644 index 3c18689c..00000000 --- a/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.scss +++ /dev/null @@ -1,10 +0,0 @@ -.pagination-ellipsis { - @apply mvx:w-8 mvx:h-8 mvx:cursor-pointer mvx:flex mvx:items-center mvx:justify-center mvx:transition-all; - @apply mvx:duration-200 mvx:rounded-full mvx:ease-in-out; - color: var(--mvx-text-color-primary); - - &:hover, - &.active { - background-color: var(--mvx-pagination-item-bg-hover); - } -} diff --git a/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.tsx b/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.tsx deleted file mode 100644 index d70ae1e5..00000000 --- a/src/components/visual/pagination/components/pagination-ellipsis/pagination-ellipsis.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, h, Prop } from '@stencil/core'; -import { ELLIPSIS } from 'constants/htmlStrings'; - -@Component({ - tag: 'mvx-pagination-ellipsis', - styleUrl: 'pagination-ellipsis.scss', - shadow: true, -}) -export class PaginationEllipsis { - @Prop() isActive: boolean = false; - - render() { - return
{ELLIPSIS}
; - } -} diff --git a/src/components/visual/pagination/pagination.scss b/src/components/visual/pagination/pagination.scss deleted file mode 100644 index 21b0cbc1..00000000 --- a/src/components/visual/pagination/pagination.scss +++ /dev/null @@ -1 +0,0 @@ -// This is needed to trigger the Stecil Tailwind compilation for inline Tailwind classes. diff --git a/src/components/visual/pagination/pagination.stories.tsx b/src/components/visual/pagination/pagination.stories.tsx deleted file mode 100644 index 57256704..00000000 --- a/src/components/visual/pagination/pagination.stories.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import './pagination.scss'; - -import { h } from '@stencil/core'; -import type { Meta, StoryObj } from '@stencil/storybook-plugin'; - -import type { Pagination } from './pagination'; - -// prettier-ignore -const styles = { - paginationStoriesWrapper: 'pagination-stories-wrapper mvx:justify-center mvx:flex mvx:gap-4 mvx:pt-24', -} satisfies Record; - -const storySettings: Meta = { - tags: ['autodocs'], - title: 'Components/Pagination', - args: { - currentPage: 1, - totalPages: 10, - isDisabled: false, - class: '', - }, - argTypes: { - currentPage: { control: { type: 'number', min: 1, max: 10 } }, - totalPages: { control: { type: 'number', min: 1 } }, - isDisabled: { control: 'boolean' }, - class: { control: 'text' }, - }, - decorators: [ - Story => ( -
- -
- ), - ], -}; - -export const Default: StoryObj = { - render: properties => , -}; - -export const Disabled: StoryObj = { - render: () => , -}; - -export const FirstPage: StoryObj = { - render: () => , -}; - -export const LastPage: StoryObj = { - render: () => , -}; - -export const SinglePage: StoryObj = { - render: () => , -}; - -export const ManyPages: StoryObj = { - render: () => , -}; - -export default storySettings; diff --git a/src/components/visual/pagination/pagination.tsx b/src/components/visual/pagination/pagination.tsx deleted file mode 100644 index 83e5fb9e..00000000 --- a/src/components/visual/pagination/pagination.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import type { EventEmitter } from '@stencil/core'; -import { Component, Event, h, Prop, State, Watch } from '@stencil/core'; -import { Icon } from 'common/Icon'; -import { DataTestIdsEnum } from 'constants/dataTestIds.enum'; - -import { getPagination } from './helpers'; -import styles from './pagination.styles'; - -@Component({ - tag: 'mvx-pagination', - styleUrl: 'pagination.scss', - shadow: true, -}) -export class Pagination { - @Prop() currentPage: number = 1; - @Prop() totalPages: number; - @Prop() isDisabled?: boolean = false; - @Prop() class?: string; - - @Event({ bubbles: false, composed: false }) pageChange: EventEmitter; - @State() activeTooltipIndex: number | null = null; - @State() isTooltipOpen: boolean = false; - @State() currentPageIndex: number; - - @Watch('currentPage') - watchCurrentPage(newValue: number) { - if (newValue !== this.currentPageIndex) { - this.currentPageIndex = newValue; - } - } - - private handleTooltipStatus(isOpen: boolean) { - this.isTooltipOpen = isOpen; - } - - private handlePageClick(newPageIndex: number) { - if (newPageIndex === this.currentPageIndex) { - return; - } - - this.currentPageIndex = newPageIndex; - this.pageChange.emit(newPageIndex); - } - - private handleEdgePageClick(pageToNavigateTo: number) { - return (event: MouseEvent) => { - event.preventDefault(); - this.handlePageClick(pageToNavigateTo); - }; - } - - private isCurrentPageActive(paginationItem: string) { - return parseFloat(paginationItem) === this.currentPageIndex; - } - - private isInTheHundreds(paginationItem: string) { - return parseFloat(paginationItem) && parseFloat(paginationItem) >= 100; - } - - componentWillLoad() { - this.currentPageIndex = this.currentPage; - } - - /** - * Generates CSS classes for pagination edge buttons (previous/next) - * @param isInactive - Whether the button should be inactive - * @returns Object with CSS class mappings - */ - - private getPaginationEdgeButtonClasses(isInactive: boolean) { - return { - [styles.paginationEdgeButton]: true, - [styles.paginationEdgeButtonDisabled]: this.isDisabled, - [styles.paginationEdgeButtonInactive]: isInactive, - }; - } - - /** - * Generates CSS classes for pagination angle buttons (first/last) - * @param isInactive - Whether the button should be inactive/disabled - * @returns Object with CSS class mappings - */ - - private getPaginationAngleClasses(isInactive: boolean) { - return { - [styles.paginationAngle]: true, - [styles.paginationAngleDisabled]: this.isDisabled, - [styles.paginationAngleInactive]: isInactive, - }; - } - - render() { - const isLeftToggleDisabled = this.currentPageIndex === 1; - const isRightToggleDisabled = this.currentPageIndex === this.totalPages; - const paginationItems = getPagination({ currentPage: this.currentPageIndex, totalPages: this.totalPages }); - - return ( -
- - - - -
- -
- -
- {paginationItems.map((paginationItem, paginationItemIndex) => ( -
- {parseFloat(paginationItem) ? ( -
this.handlePageClick(Number(paginationItem))} - data-testid={`${DataTestIdsEnum.paginationItem}-${paginationItem}`} - class={{ - [styles.paginationItem]: true, - [styles.paginationItemBefore]: true, - [styles.paginationItemActive]: this.isCurrentPageActive(paginationItem), - [styles.paginationItemHundreds]: this.isInTheHundreds(paginationItem), - }} - > - {paginationItem} -
- ) : ( - - } - onTriggerRender={(event: CustomEvent) => { - this.activeTooltipIndex = paginationItemIndex; - this.handleTooltipStatus(event.detail); - }} - > - {!this.isDisabled && ( - this.handlePageClick(event.detail)} - /> - )} - - )} -
- ))} -
- -
- -
- - - - -
- ); - } -} diff --git a/src/global/tailwind.css b/src/global/tailwind.css index a78f6c6d..cfea5f3b 100644 --- a/src/global/tailwind.css +++ b/src/global/tailwind.css @@ -109,4 +109,10 @@ .mvx\:animate-spinner { animation: SpinnerAnimation 3000ms linear infinite; } + + .mvx\:no-spinner::-webkit-outer-spin-button, + .mvx\:no-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4dbf3626..cd4cd292 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,7 @@ "jsxFactory": "h" }, "include": ["src"], - "exclude": ["node_modules"] + "exclude": ["node_modules", + "src/components/visual/address-table/components/pagination/pagination-story.tsx" + ] }