From 4c11e6cca7cd52920ff4cfc9255916d11fe764b4 Mon Sep 17 00:00:00 2001 From: yasha-meursault Date: Thu, 5 Feb 2026 16:17:34 +0400 Subject: [PATCH 1/2] change the leaflet to vaul --- components/LayerswapMenu/index.tsx | 113 +++++++------- components/Modal/modalWithoutAnimation.tsx | 138 ++++++++++++++++++ .../Select/Command/CommandSelectWrapper.tsx | 4 - components/Select/Command/commandSelect.tsx | 16 +- components/Select/Selector/Index.tsx | 46 ++++++ components/Select/Selector/SelectItem.tsx | 83 +++++++++++ components/Widget/Index.tsx | 6 +- components/Wizard/Wizard.tsx | 6 +- 8 files changed, 332 insertions(+), 80 deletions(-) create mode 100644 components/Modal/modalWithoutAnimation.tsx create mode 100644 components/Select/Selector/Index.tsx create mode 100644 components/Select/Selector/SelectItem.tsx diff --git a/components/LayerswapMenu/index.tsx b/components/LayerswapMenu/index.tsx index d502e78e..cf93b524 100644 --- a/components/LayerswapMenu/index.tsx +++ b/components/LayerswapMenu/index.tsx @@ -1,5 +1,5 @@ import { MenuIcon, ChevronLeft } from "lucide-react"; -import { FC, useState } from "react"; +import { FC, useEffect, useState } from "react"; import IconButton from "../buttons/iconButton"; import { FormWizardProvider, useFormWizardaUpdate, useFormWizardState } from "../../context/formWizardProvider"; import { MenuStep } from "../../Models/Wizard"; @@ -8,29 +8,20 @@ import Wizard from "../Wizard/Wizard"; import WizardItem from "../Wizard/WizardItem"; import { NextRouter, useRouter } from "next/router"; import { resolvePersistantQueryParams } from "../../helpers/querryHelper"; -import Modal from "../Modal/modal"; +import { Modal, ModalContent } from "../Modal/modalWithoutAnimation"; import RpcNetworkListView from "../Settings/RpcNetworkListView"; import NetworkRpcEditView from "../Settings/NetworkRpcEditView"; import { Network } from "../../Models/Network"; -import clsx from "clsx"; const Comp = () => { const router = useRouter(); + const [isOpen, setIsOpen] = useState(false); const { goBack, currentStepName } = useFormWizardState() const { goToStep } = useFormWizardaUpdate() - const [openTopModal, setOpenTopModal] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState(null); - const handleModalOpenStateChange = (value: boolean) => { - setOpenTopModal(value) - if (value === false) { - goToStep(MenuStep.Menu) - setSelectedNetwork(null) - } - } - const goBackToMenuStep = () => { goToStep(MenuStep.Menu, "back"); clearMenuPath(router) } const goBackToRpcConfiguration = () => { goToStep(MenuStep.RPCConfiguration, "back") } @@ -51,56 +42,60 @@ const Comp = () => { goToStep(MenuStep.RPCConfiguration, "back") } + useEffect(() => { + if (!isOpen) { + goToStep(MenuStep.Menu) + setSelectedNetwork(null) + clearMenuPath(router) + } + }, [isOpen]) + return <>
- setOpenTopModal(true)} icon={ - - }> - - - { - goBack && -
- - }> - -
- } -

{currentStepName as string}

-
- } - > - + setIsOpen(true)} icon={ + + } /> + + + + { + goBack && +
+ + } /> +
+ } +

{currentStepName as string}

+ + } > - - - - - - - - {selectedNetwork ? ( - - ) : ( -
Loading...
- )} -
- {/* - handleModalOpenStateChange(false)} /> - */} -
+ {() => ( +
+ + + + + + + + + {selectedNetwork ? ( + + ) : ( +
Loading...
+ )} +
+
+
+ )} + diff --git a/components/Modal/modalWithoutAnimation.tsx b/components/Modal/modalWithoutAnimation.tsx new file mode 100644 index 00000000..5fb8ca67 --- /dev/null +++ b/components/Modal/modalWithoutAnimation.tsx @@ -0,0 +1,138 @@ +import { createContext, DetailedHTMLProps, HTMLAttributes, ReactNode, SetStateAction, useContext, useEffect, useState } from "react"; +import { createPortal } from "react-dom"; +import useWindowDimensions from "@/hooks/useWindowDimensions"; +import IconButton from "@/components/buttons/iconButton"; +import { X } from 'lucide-react'; +import clsx from "clsx"; + +type ModalProps = { + setIsOpen: (value: SetStateAction) => void; + isOpen: boolean + shouldFocus: boolean; + setShouldFocus: (value: SetStateAction) => void; +} + +const ModalContext = createContext({ isOpen: false, setIsOpen: () => { }, shouldFocus: false, setShouldFocus: () => { } }); + +export const Modal = ({ children, isOpen: _isOpen, setIsOpen: _setIsOpen }: { children: ReactNode, isOpen?: ModalProps['isOpen'], setIsOpen?: ModalProps['setIsOpen'] }) => { + const [isOpen, setIsOpen] = useState(false); + const [shouldFocus, setShouldFocus] = useState(false); + + return ( + + {children} + + ); +}; + +export const useModalState = () => { + const context = useContext(ModalContext); + if (!context) { + throw new Error("useModalState must be used within a Modal"); + } + return context; +} + +type ContentChildProps = { + closeModal: () => void; + shouldFocus: boolean; +} + +type ModalContentProps = { + header?: ReactNode; + children: ((props: ContentChildProps) => JSX.Element) | JSX.Element; + className?: string; + showCloseButton?: boolean; +} + +export const ModalContent = (props: ModalContentProps) => { + const { children, header, className = "", showCloseButton = true } = props + const { isOpen, setIsOpen, setShouldFocus, shouldFocus } = useModalState(); + const closeModal = () => { setIsOpen(false); setShouldFocus(false) }; + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + closeModal(); + } + }; + + if (isOpen) { + document.addEventListener("keydown", handleKeyDown); + } + + return () => document.removeEventListener("keydown", handleKeyDown); + }, [isOpen]); + + if (!isOpen) return null; + + const modalElement = ( +
+ {(header || showCloseButton) && ( +
+
+
+ {header} +
+ {showCloseButton && ( +
+ + }> + +
+ )} +
+
+ )} + +
+ {typeof children === 'function' ? children({ closeModal, shouldFocus }) : children} +
+
+ ); + + const widgetElement = document.getElementById('widget'); + + if (!widgetElement) { + console.warn('Widget element not found, modal will not render'); + return null; + } + + return createPortal(modalElement, widgetElement); +} + +type ModalTriggerProps = DetailedHTMLProps, HTMLDivElement> & { + disabled?: boolean; + children: React.ReactNode | React.ReactNode[]; + className?: string; + onClick?: () => void; +} + +export const ModalTrigger = (props: ModalTriggerProps) => { + const { disabled = false, children, className = "", onClick, ...rest } = props + const { setIsOpen, setShouldFocus } = useContext(ModalContext); + const { isDesktop } = useWindowDimensions(); + + function openModal() { + setIsOpen(true) + isDesktop && setShouldFocus(true) + onClick?.(); + } + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/components/Select/Command/CommandSelectWrapper.tsx b/components/Select/Command/CommandSelectWrapper.tsx index 7de32ae6..59f64dca 100644 --- a/components/Select/Command/CommandSelectWrapper.tsx +++ b/components/Select/Command/CommandSelectWrapper.tsx @@ -3,7 +3,6 @@ import Image from 'next/image' import { ChevronDown } from 'lucide-react' import { ISelectMenuItem, SelectMenuItem } from '../Shared/Props/selectMenuItem' import CommandSelect, { SelectMenuItemGroup } from './commandSelect' -import { LeafletHeight } from '../../Modal/leaflet' type CommandSelectWrapperProps = { setValue: (value: ISelectMenuItem) => void; @@ -14,7 +13,6 @@ type CommandSelectWrapperProps = { disabled: boolean; valueGrouper: (values: ISelectMenuItem[]) => SelectMenuItemGroup[]; isLoading: boolean; - modalHeight?: LeafletHeight; valueDetails?: React.ReactNode; modalContent?: React.ReactNode; direction?: string; @@ -30,7 +28,6 @@ export default function CommandSelectWrapper({ values, valueGrouper, isLoading, - modalHeight, modalContent, header, valueDetails @@ -95,7 +92,6 @@ export default function CommandSelectWrapper({ valueGrouper={valueGrouper} values={values} isLoading={isLoading} - modalHeight={modalHeight} modalContent={modalContent} header={header} /> diff --git a/components/Select/Command/commandSelect.tsx b/components/Select/Command/commandSelect.tsx index f8e6dc6c..d27f0d36 100644 --- a/components/Select/Command/commandSelect.tsx +++ b/components/Select/Command/commandSelect.tsx @@ -11,9 +11,8 @@ import React, { useCallback } from "react"; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import SelectItem from '../Shared/SelectItem'; import { SelectProps } from '../Shared/Props/SelectProps' -import Modal from '../../Modal/modal'; +import { Modal, ModalContent } from '../../Modal/modalWithoutAnimation'; import SpinIcon from '../../Icons/spinIcon'; -import { LeafletHeight } from '../../Modal/leaflet'; export interface CommandSelectProps extends SelectProps { show: boolean; @@ -21,7 +20,6 @@ export interface CommandSelectProps extends SelectProps { searchHint: string; valueGrouper: (values: ISelectMenuItem[]) => SelectMenuItemGroup[]; isLoading: boolean; - modalHeight?: LeafletHeight; modalContent?: React.ReactNode; header?: string; } @@ -35,7 +33,7 @@ export class SelectMenuItemGroup { items: ISelectMenuItem[]; } -export default function CommandSelect({ values, setValue, show, setShow, searchHint, valueGrouper, isLoading, modalHeight = 'full', modalContent, header }: CommandSelectProps) { +export default function CommandSelect({ values, setValue, show, setShow, searchHint, valueGrouper, isLoading, modalContent, header }: CommandSelectProps) { const { isDesktop } = useWindowDimensions(); let groups: SelectMenuItemGroup[] = valueGrouper(values); @@ -45,11 +43,8 @@ export default function CommandSelect({ values, setValue, show, setShow, searchH }, [setValue, setShow]); return ( - - {header ?
-
{header}
-
: <>} - {show ? + + {searchHint && } {modalContent} @@ -76,8 +71,7 @@ export default function CommandSelect({ values, setValue, show, setShow, searchH } - : <> - } + ) } \ No newline at end of file diff --git a/components/Select/Selector/Index.tsx b/components/Select/Selector/Index.tsx new file mode 100644 index 00000000..6c941859 --- /dev/null +++ b/components/Select/Selector/Index.tsx @@ -0,0 +1,46 @@ +import { ReactNode } from "react"; +import { Modal, ModalContent, ModalTrigger, useModalState } from "@/components/Modal/modalWithoutAnimation"; + +export const Selector = ({ children }) => { + return ( + + {children} + + ); +}; + +export const useSelectorState = () => { + return useModalState(); +} + +type ContentChildProps = { + closeModal: () => void; + shouldFocus: boolean; +} + +type SelectContentProps = { + header?: ReactNode; + searchHint?: string; + children: ((props: ContentChildProps) => JSX.Element); + isLoading: boolean; +} + +export const SelectorContent = (props: SelectContentProps) => { + const { children, header } = props; + + return ( + + {children} + + ); +} + +type SelectTriggerProps = { + disabled: boolean; + children: React.ReactNode | React.ReactNode[]; + className?: string; +} + +export const SelectorTrigger = (props: SelectTriggerProps) => { + return ; +} diff --git a/components/Select/Selector/SelectItem.tsx b/components/Select/Selector/SelectItem.tsx new file mode 100644 index 00000000..9ad48e2f --- /dev/null +++ b/components/Select/Selector/SelectItem.tsx @@ -0,0 +1,83 @@ +import { ImageWithFallback } from '@/components/Common/ImageWithFallback'; +import clsx from 'clsx'; +import { ReactNode } from 'react'; + +type SelectItemWrapperProps = { + className?: string; + children: JSX.Element | JSX.Element[]; +} +const SelectItem = ({ children, className }: SelectItemWrapperProps) => { + return
+ {children} +
+} +type SelectItemLogoProps = { + imgSrc?: string; + altText: string; + className?: string; +} +const Logo = ({ imgSrc, altText, className = 'rounded-md' }: SelectItemLogoProps) => { + return
+ {imgSrc ?
+ +
+ : +
+ } +
+} + +type SelectItemTitleProps = { + className?: string; + children?: ReactNode; +} +const Title = ({ children, className }: SelectItemTitleProps) => { + return
+ {children} +
+} + +type SelectItemDetailedTitleProps = { + className?: string; + children?: ReactNode; + title: ReactNode; + secondary: ReactNode; + secondaryImageAlt: string; + secondaryLogoSrc: string | undefined; + logoClassName?: string; +} + +const DetailedTitle = ({ children, className, title, secondary, secondaryImageAlt, secondaryLogoSrc, logoClassName }: SelectItemDetailedTitleProps) => { + return + <div className="col-span-9 flex flex-col gap-1 leading-5 align-middle font-medium"> + <div className="align-middle leading-5 text-base flex items-center justify-between w-full min-w-0">{title}</div> + </div> + <div className="col-span-5 sm:col-span-6 flex items-center gap-1 min-w-0 overflow-hidden pr-2"> + {secondaryLogoSrc && <ImageWithFallback + src={secondaryLogoSrc} + alt={secondaryImageAlt} + height="16" + width="16" + loading="eager" + className={clsx("h-4 w-4 object-contain rounded shrink-0", logoClassName)} + />} + <div className="text-secondary-text text-xs min-w-0 whitespace-nowrap"> + {secondary} + </div> + </div> + {children && <div className="col-span-4 sm:col-span-3 text-right truncate">{children}</div>} + +} + +SelectItem.Logo = Logo +SelectItem.Title = Title +SelectItem.DetailedTitle = DetailedTitle + +export { SelectItem } \ No newline at end of file diff --git a/components/Widget/Index.tsx b/components/Widget/Index.tsx index 29d8f28d..9d40f448 100644 --- a/components/Widget/Index.tsx +++ b/components/Widget/Index.tsx @@ -3,8 +3,8 @@ import { useRouter } from "next/router" import { default as Content } from './Content'; import { default as Footer } from './Footer'; import { useCallback, useRef } from "react"; -import { resolvePersistantQueryParams } from "../../helpers/querryHelper"; -import AppSettings from "../../lib/AppSettings"; +import { resolvePersistantQueryParams } from "@/helpers/querryHelper"; +import AppSettings from "@/lib/AppSettings"; type Props = { children: JSX.Element | JSX.Element[]; @@ -29,7 +29,7 @@ const Widget = ({ children, className, hideMenu }: Props) => { const handleBack = router.pathname === "/" ? null : goBack return <> -
+
{ AppSettings.ApiVersion === 'sandbox' && diff --git a/components/Wizard/Wizard.tsx b/components/Wizard/Wizard.tsx index e79c5260..e88d8656 100644 --- a/components/Wizard/Wizard.tsx +++ b/components/Wizard/Wizard.tsx @@ -1,8 +1,8 @@ import { FC, useEffect, useRef } from 'react' -import { useFormWizardaUpdate, useFormWizardState } from '../../context/formWizardProvider'; +import { useFormWizardaUpdate, useFormWizardState } from '@/context/formWizardProvider'; import { AnimatePresence } from 'framer-motion'; import HeaderWithMenu from '../HeaderWithMenu'; -import AppSettings from '../../lib/AppSettings'; +import AppSettings from '@/lib/AppSettings'; type Props = { children: JSX.Element | JSX.Element[]; @@ -31,7 +31,7 @@ const Wizard: FC = ({ children, wizardId, className }) => { const width = positionPercent || 0 return <> -
+
{ AppSettings.ApiVersion === 'sandbox' && !noToolBar && From b541b5debde34711eb8a61ae06bcfd4842200437 Mon Sep 17 00:00:00 2001 From: yasha-meursault Date: Thu, 5 Feb 2026 18:11:44 +0400 Subject: [PATCH 2/2] additional fixes --- components/HeaderWithMenu/index.tsx | 10 +++++----- components/LayerswapMenu/MenuList.tsx | 16 ++++++++-------- components/LayerswapMenu/index.tsx | 18 +++++++++--------- components/Modal/modalWithoutAnimation.tsx | 2 +- .../AtomicContent/Summary/Summary.tsx | 10 +++++----- components/Wallet/ConnectedWallets.tsx | 12 ++++++------ context/snapPointsContext.tsx | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/components/HeaderWithMenu/index.tsx b/components/HeaderWithMenu/index.tsx index bf88f295..9f71f741 100644 --- a/components/HeaderWithMenu/index.tsx +++ b/components/HeaderWithMenu/index.tsx @@ -1,11 +1,11 @@ import { useIntercom } from "react-use-intercom" -import IconButton from "../buttons/iconButton" -import GoHomeButton from "../utils/GoHome" +import IconButton from "@/components/buttons/iconButton" +import GoHomeButton from "@/components/utils/GoHome" import { ArrowLeft } from 'lucide-react' -import ChatIcon from "../Icons/ChatIcon" +import ChatIcon from "@/components/Icons/ChatIcon" import dynamic from "next/dynamic" -import LayerswapMenu from "../LayerswapMenu" -import { useQueryState } from "../../context/query" +import LayerswapMenu from "@/components/LayerswapMenu" +import { useQueryState } from "@/context/query" const WalletsHeader = dynamic(() => import("../Wallet/ConnectedWallets.tsx").then((comp) => comp.WalletsHeader), { loading: () => <> diff --git a/components/LayerswapMenu/MenuList.tsx b/components/LayerswapMenu/MenuList.tsx index 52dd25db..28a446c2 100644 --- a/components/LayerswapMenu/MenuList.tsx +++ b/components/LayerswapMenu/MenuList.tsx @@ -2,17 +2,17 @@ import { BookOpen, Home, LibraryIcon, Shield, MessageSquarePlus, CircleHelp, Inf import { useRouter } from "next/router"; import { FC, useEffect, useState } from "react"; import { useIntercom } from "react-use-intercom"; -import ChatIcon from "../Icons/ChatIcon"; -import inIframe from "../utils/inIframe"; -import GitHubLogo from "../Icons/GitHubLogo"; -import TwitterLogo from "../Icons/TwitterLogo"; +import ChatIcon from "@/components/Icons/ChatIcon"; +import inIframe from "@/components/utils/inIframe"; +import GitHubLogo from "@/components/Icons/GitHubLogo"; +import TwitterLogo from "@/components/Icons/TwitterLogo"; import Link from "next/link"; -import Popover from "../Modal/popover"; -import SendFeedback from "../sendFeedback"; +import Popover from "@/components/Modal/popover"; +import SendFeedback from "@/components/sendFeedback"; import Menu from "./Menu"; import dynamic from "next/dynamic"; -import { MenuStep } from "../../Models/Wizard"; -import useWindowDimensions from "../../hooks/useWindowDimensions"; +import { MenuStep } from "@/Models/Wizard"; +import useWindowDimensions from "@/hooks/useWindowDimensions"; const WalletsMenu = dynamic(() => import("../Wallet/ConnectedWallets.tsx").then((comp) => comp.WalletsMenu), { loading: () => <> diff --git a/components/LayerswapMenu/index.tsx b/components/LayerswapMenu/index.tsx index cf93b524..11f2ff1d 100644 --- a/components/LayerswapMenu/index.tsx +++ b/components/LayerswapMenu/index.tsx @@ -1,17 +1,17 @@ import { MenuIcon, ChevronLeft } from "lucide-react"; import { FC, useEffect, useState } from "react"; -import IconButton from "../buttons/iconButton"; -import { FormWizardProvider, useFormWizardaUpdate, useFormWizardState } from "../../context/formWizardProvider"; -import { MenuStep } from "../../Models/Wizard"; +import IconButton from "@/components/buttons/iconButton"; +import { FormWizardProvider, useFormWizardaUpdate, useFormWizardState } from "@/context/formWizardProvider"; +import { MenuStep } from "@/Models/Wizard"; import MenuList from "./MenuList"; -import Wizard from "../Wizard/Wizard"; +import Wizard from "@/components/Wizard/Wizard"; import WizardItem from "../Wizard/WizardItem"; import { NextRouter, useRouter } from "next/router"; -import { resolvePersistantQueryParams } from "../../helpers/querryHelper"; -import { Modal, ModalContent } from "../Modal/modalWithoutAnimation"; -import RpcNetworkListView from "../Settings/RpcNetworkListView"; -import NetworkRpcEditView from "../Settings/NetworkRpcEditView"; -import { Network } from "../../Models/Network"; +import { resolvePersistantQueryParams } from "@/helpers/querryHelper"; +import { Modal, ModalContent } from "@/components/Modal/modalWithoutAnimation"; +import RpcNetworkListView from "@/components/Settings/RpcNetworkListView"; +import NetworkRpcEditView from "@/components/Settings/NetworkRpcEditView"; +import { Network } from "@/Models/Network"; const Comp = () => { const router = useRouter(); diff --git a/components/Modal/modalWithoutAnimation.tsx b/components/Modal/modalWithoutAnimation.tsx index 5fb8ca67..d5ee76cf 100644 --- a/components/Modal/modalWithoutAnimation.tsx +++ b/components/Modal/modalWithoutAnimation.tsx @@ -70,7 +70,7 @@ export const ModalContent = (props: ModalContentProps) => {
{(header || showCloseButton) && (
-
+
{header}
diff --git a/components/Swap/AtomicChat/AtomicContent/Summary/Summary.tsx b/components/Swap/AtomicChat/AtomicContent/Summary/Summary.tsx index 79db02a3..13b90e67 100644 --- a/components/Swap/AtomicChat/AtomicContent/Summary/Summary.tsx +++ b/components/Swap/AtomicChat/AtomicContent/Summary/Summary.tsx @@ -1,10 +1,10 @@ import Image from "next/image"; import { FC } from "react"; -import { truncateDecimals } from "../../../../utils/RoundDecimals"; -import { Network, Token } from "../../../../../Models/Network"; -import { addressFormat } from "../../../../../lib/address/formatter"; -import { ExtendedAddress } from "../../../../Input/Address/AddressPicker/AddressWithIcon"; -import { isValidAddress } from "../../../../../lib/address/validator"; +import { truncateDecimals } from "@/components/utils/RoundDecimals"; +import { Network, Token } from "@/Models/Network"; +import { addressFormat } from "@/lib/address/formatter"; +import { ExtendedAddress } from "@/components/Input/Address/AddressPicker/AddressWithIcon"; +import { isValidAddress } from "@/lib/address/validator"; type AtomicSummaryProps = { sourceCurrency: Token, diff --git a/components/Wallet/ConnectedWallets.tsx b/components/Wallet/ConnectedWallets.tsx index b0797c39..52406109 100644 --- a/components/Wallet/ConnectedWallets.tsx +++ b/components/Wallet/ConnectedWallets.tsx @@ -1,11 +1,11 @@ -import WalletIcon from "../Icons/WalletIcon" -import useWallet from "../../hooks/useWallet" -import ConnectButton from "../buttons/connectButton" +import WalletIcon from "@/components/Icons/WalletIcon" +import useWallet from "@/hooks/useWallet" +import ConnectButton from "@/components/buttons/connectButton" import { useState } from "react" import WalletsList from "./WalletsList" -import { Wallet } from "../../Models/WalletProvider" -import VaulDrawer from "../Modal/vaulModal" -import shortenAddress from "../utils/ShortenAddress" +import { Wallet } from "@/Models/WalletProvider" +import VaulDrawer from "@/components/Modal/vaulModal" +import shortenAddress from "@/components/utils/ShortenAddress" export const WalletsHeader = () => { const { wallets } = useWallet() diff --git a/context/snapPointsContext.tsx b/context/snapPointsContext.tsx index 3fc511a0..0bdd76a6 100644 --- a/context/snapPointsContext.tsx +++ b/context/snapPointsContext.tsx @@ -74,7 +74,7 @@ const resolveSnapPoints = ({ isMobile, snapPointsCount, childrenHeights, headerH return [{ id: i + 1, height: `${totalHeight}px` }] } - if ((pointHeight && viewportHeight) && pointHeight > (viewportHeight * .98)) { + if ((pointHeight && viewportHeight) && pointHeight > (viewportHeight * .9)) { points.push({ id: i + 1, height: 1 }); break; }