diff --git a/lib/components/Header/Header.tsx b/lib/components/Header/Header.tsx index fd63665..9f9c929 100644 --- a/lib/components/Header/Header.tsx +++ b/lib/components/Header/Header.tsx @@ -1,10 +1,10 @@ -import SettingPopover from "./SettingPopover"; -import "./Header.scss"; -import { useOptionsStore } from "../../store/options.store"; -import Refresh from "../Common/Refresh"; -import { useSwapStore } from "../../store/swap.store"; -import { useTranslation } from "react-i18next"; -import Setting from "../icons/Setting"; +import SettingPopover from './SettingPopover'; +import './Header.scss'; +import { useOptionsStore } from '../../store/options.store'; +import Refresh from '../Common/Refresh'; +import { useSwapStore } from '../../store/swap.store'; +import { useTranslation } from 'react-i18next'; +import Menu from '../icons/Menu'; const Header = () => { const { t } = useTranslation(); const { options } = useOptionsStore(); @@ -12,12 +12,12 @@ const Header = () => { useSwapStore(); return ( -
-
{t("swap")}
-
+
+
{t('swap')}
+
{options.ui_preferences?.show_refresh && (
{ )} {options.ui_preferences?.show_settings && ( - + )}
diff --git a/lib/components/Header/SettingPopover.tsx b/lib/components/Header/SettingPopover.tsx index f818ec4..07c8eb2 100644 --- a/lib/components/Header/SettingPopover.tsx +++ b/lib/components/Header/SettingPopover.tsx @@ -1,16 +1,16 @@ -import { FC, PropsWithChildren, useRef, useState } from "react"; -import SlippageSetting from "./SlippageSetting"; -import { useOnClickOutside } from "usehooks-ts"; -import TokensSettings from "./TokensSettings"; -import { AnimatePresence, motion } from "framer-motion"; +import { FC, PropsWithChildren, useRef, useState } from 'react'; +import SlippageSetting from './SlippageSetting'; +import { useOnClickOutside } from 'usehooks-ts'; +import TokensSettings from './TokensSettings'; +import { AnimatePresence, motion } from 'framer-motion'; -import "./SettingPopover.scss"; -import Wallet from "./Wallet"; -import { useOptionsStore } from "../../store/options.store"; -import { useTranslation } from "react-i18next"; -import { useMediaQuery } from "@uidotdev/usehooks"; -import { modalAnimationDesktop, modalAnimationMobile } from "../../constants"; -import { IoClose } from "react-icons/io5"; +import './SettingPopover.scss'; +import Wallet from './Wallet'; +import { useOptionsStore } from '../../store/options.store'; +import { useTranslation } from 'react-i18next'; +import { useMediaQuery } from '@uidotdev/usehooks'; +import { modalAnimationDesktop, modalAnimationMobile } from '../../constants'; +import Close from '../icons/Close'; export type SettingPopoverProps = PropsWithChildren & {}; @@ -36,14 +36,18 @@ const SettingPopover: FC = ({ children }) => { const handleCloseSetting = () => { setIsOpen(false); }; - const isDesktop = useMediaQuery("(min-width: 768px)"); + const isDesktop = useMediaQuery('(min-width: 768px)'); const modalAnimation = isDesktop ? modalAnimationDesktop : modalAnimationMobile; return ( -
- @@ -52,25 +56,27 @@ const SettingPopover: FC = ({ children }) => { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - transition={{ ease: "easeOut", duration: 0.15 }} - className="modal-backdrop" + transition={{ ease: 'easeOut', duration: 0.15 }} + className="mts-fixed mts-top-0 mts-left-0 mts-bg-black/50 mts-w-full mts-h-full" > -
-
{t("setting")}
+
+
+ {t('setting')} +
diff --git a/lib/components/Header/SlippageSetting.tsx b/lib/components/Header/SlippageSetting.tsx index 4750e1a..95d73c7 100644 --- a/lib/components/Header/SlippageSetting.tsx +++ b/lib/components/Header/SlippageSetting.tsx @@ -1,8 +1,8 @@ import { ChangeEvent, useState } from 'react'; -import { AnimatePresence, motion } from 'framer-motion'; import { useSwapStore } from '../../store/swap.store'; import './SlippageSetting.scss'; import { useTranslation } from 'react-i18next'; +import { cn } from '../../utils/cn'; const SlippageSetting = () => { const { t } = useTranslation(); @@ -50,72 +50,77 @@ const SlippageSetting = () => { return (
- - - +
+ {t('auto')} +
+
handleOnPercentClick(2)} + className={cn( + `mts-flex mts-relative mts-justify-center mts-items-center mts-transition-all mts-duration-300 mts-cursor-pointer mts-m-[0.125rem] mts-border-[1px] mts-border-transparent mts-rounded-lg mts-bg-white dark:mts-bg-dark-800 dark:mts-text-white mts-p-1 mts-h-12 md:mts-h-12`, + slippage === 2 + ? 'mts-border-primary-500' + : 'mts-border-dark-200 dark:mts-border-dark-700' + )} + data-testid="slippage-setting-2" + > + 2% +
+
handleOnPercentClick(5)} + className={cn( + `mts-flex mts-relative mts-justify-center mts-items-center mts-transition-all mts-duration-300 mts-cursor-pointer mts-m-[0.125rem] mts-border-[1px] mts-border-transparent mts-rounded-lg mts-bg-white dark:mts-bg-dark-800 dark:mts-text-white mts-p-1 mts-h-12 md:mts-h-12`, + slippage === 5 + ? 'mts-border-primary-500' + : 'mts-border-dark-200 dark:mts-border-dark-700' + )} + data-testid="slippage-setting-5" + > + 5% +
+
-
- {t('auto')} -
-
handleOnPercentClick(2)} - className={`dropdown-item ${ - slippage === 2 ? 'active' : 'inactive-border' - }`} - data-testid="slippage-setting-2" - > - 2% -
-
handleOnPercentClick(5)} - className={`dropdown-item ${ - slippage === 5 ? 'active' : 'inactive-border' - }`} - data-testid="slippage-setting-5" - > - 5% -
-
- - % -
- - + + % +
+
); }; diff --git a/lib/components/Header/TokensSettings.tsx b/lib/components/Header/TokensSettings.tsx index fab0829..d5f508e 100644 --- a/lib/components/Header/TokensSettings.tsx +++ b/lib/components/Header/TokensSettings.tsx @@ -1,30 +1,33 @@ -import clsx from "clsx"; -import { useSwapStore } from "../../store/swap.store"; -import "./TokensSettings.scss"; -import { useTranslation } from "react-i18next"; +import { useSwapStore } from '../../store/swap.store'; +import { useTranslation } from 'react-i18next'; +import { cn } from '../../utils/cn'; + const TokensSettings = () => { const { t } = useTranslation(); const { communityTokens, setCommunityTokens } = useSwapStore(); return ( -
-
- {t("community_tokens")} +
+
+ {t('community_tokens')}
diff --git a/lib/components/Header/Wallet.tsx b/lib/components/Header/Wallet.tsx index 7e37044..2ecebc4 100644 --- a/lib/components/Header/Wallet.tsx +++ b/lib/components/Header/Wallet.tsx @@ -1,17 +1,23 @@ -import { fromNano } from "@mytonswap/sdk"; -import { TON_ADDR } from "../../constants"; -import { useWalletStore } from "../../store/wallet.store"; -import "./Wallet.scss"; -import formatNumber from "../../utils/formatNum"; -import shortAddress from "../../utils/shortAddress"; -import { FaCheck, FaCopy } from "react-icons/fa6"; -import { useState } from "react"; -import { MdArrowOutward } from "react-icons/md"; -import { useOptionsStore } from "../../store/options.store"; -import { useTranslation } from "react-i18next"; +import { fromNano } from '@mytonswap/sdk'; +import { TON_ADDR } from '../../constants'; +import { useWalletStore } from '../../store/wallet.store'; +import './Wallet.scss'; +import formatNumber from '../../utils/formatNum'; +import shortAddress from '../../utils/shortAddress'; +import { FaCheck } from 'react-icons/fa6'; +import { FC, useState } from 'react'; +import { useOptionsStore } from '../../store/options.store'; +import { useTranslation } from 'react-i18next'; +import Copy from '../icons/Copy'; +import Link from '../icons/Link'; +import { cn } from '../../utils/cn'; // import { useTonConnectUI } from "@tonconnect/ui-react"; -const Wallet = () => { +type WalletProps = { + isWalletPopover?: boolean; +}; + +const Wallet: FC = ({ isWalletPopover }) => { // make function and state for copy to clipboard address button const { t } = useTranslation(); const [copied, setCopied] = useState(false); @@ -43,51 +49,71 @@ const Wallet = () => { ); return ( - <> - {wallet && ( -
-

{t("account")}

-
-

{t("balance")}

-
+ wallet && ( +
+
+

+ {t('account')} +

+
+

+ {t('balance')} +

+
{TON_BALANCE} - {t("ton")} + + {t('ton')} +
-
-
+
+
{shortAddress( wallet.account.address, - "mainnet", + 'mainnet', 4 )}
- +
-
- )} - + +
+ ) ); }; diff --git a/lib/components/Swap/Swap.scss b/lib/components/Swap/Swap.scss index 75aa7b2..bbe9311 100644 --- a/lib/components/Swap/Swap.scss +++ b/lib/components/Swap/Swap.scss @@ -1,108 +1,3 @@ -:root { - --font-size-xxs: 0.625rem; - --font-size-xs: 0.75rem; - --font-size-sm: 0.875rem; - --font-size-base: 1rem; - --font-size-lg: 1.125rem; - --font-size-xl: 1.25rem; - --font-size-2xl: 1.5rem; - --font-size-3xl: 1.875rem; - --font-size-4xl: 2.25rem; - --font-size-5xl: 3rem; - --font-size-6xl: 4rem; - --font-size-7xl: 5rem; - --font-size-8xl: 6rem; - --font-size-9xl: 7rem; - --font-size-10xl: 8rem; -} - -.mytonswap-app { - font-family: 'Plus Jakarta Sans', sans-serif; - * { - font-family: 'Plus Jakarta Sans', sans-serif !important; - -webkit-tap-highlight-color: transparent; - } - button { - cursor: pointer; - border: none; - background: transparent; - font-family: Inter, sans-serif; - } - input { - outline: none; - border: none; - background: none; - width: 100%; - } - * { - box-sizing: border-box; - margin: 0; - padding: 0; - } - - .animate-spin { - animation: spin 1s linear infinite; - color: var(--primary-color); - } - - .animate-loading { - animation: spin 1s linear infinite; - } - - @keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } - .line-clamp-1 { - display: -webkit-box; - overflow: hidden; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - line-clamp: 1; - } - .container { - border-width: 1px; - border-style: solid; - border-color: var(--swap-container-border-color); - border-radius: 1rem; - background: var(--swap-container-background-color); - padding: 0.5rem; - max-width: 21.875rem; - overflow: hidden; - } - @media screen and (min-width: 768px) { - .container { - padding: 0.75rem; - width: 28.125rem; - max-width: 28.125rem; - } - } - @media screen and (min-width: 1024px) { - .container { - width: calc(34.375rem + 24px); - max-width: calc(34.375rem + 24px); - } - } - - .text-provided { - margin-top: 0.5rem; - margin-bottom: 0.25rem; - width: 100%; - color: var(--text-black-color); - font-size: var(--font-size-xxs); - text-align: center; - a { - opacity: 0.7; - color: inherit; - text-decoration: none; - } - } -} - .mytonswap-app.rtl { direction: rtl; } diff --git a/lib/components/Swap/Swap.stories.tsx b/lib/components/Swap/Swap.stories.tsx index 695c47e..55734e5 100644 --- a/lib/components/Swap/Swap.stories.tsx +++ b/lib/components/Swap/Swap.stories.tsx @@ -29,6 +29,13 @@ export default meta; type Story = StoryObj; export const Default: Story = { + args: { + options: { + ui_preferences: { + primary_color: '#e82113', + }, + }, + }, decorators: [ (Story) => (
diff --git a/lib/components/Swap/Swap.tsx b/lib/components/Swap/Swap.tsx index 2e96e04..d40ac93 100644 --- a/lib/components/Swap/Swap.tsx +++ b/lib/components/Swap/Swap.tsx @@ -161,13 +161,17 @@ export const SwapComponent: FC = ({ return ( Something went wrong
}>
-
+
- {shouldShowSwapDetails && } + {shouldShowSwapDetails && } {shouldShowProvidedText && ( -
+
{t('service_provided')}{' '} = ({ diff --git a/lib/components/SwapButton/ConfirmationModal.tsx b/lib/components/SwapButton/ConfirmationModal.tsx index d40b151..a776bbb 100644 --- a/lib/components/SwapButton/ConfirmationModal.tsx +++ b/lib/components/SwapButton/ConfirmationModal.tsx @@ -1,20 +1,28 @@ -import { IoMdClose } from "react-icons/io"; -import { ModalState, useSwapStore } from "../../store/swap.store"; -import { fromNano } from "@mytonswap/sdk"; -import { FaArrowRightArrowLeft } from "react-icons/fa6"; -import SwapKeyValue from "../SwapDetails/SwapKeyValue"; -import formatNumber from "../../utils/formatNum"; -import swap from "../../utils/swap"; -import { FC, useEffect } from "react"; -import "./ConfirmationModal.scss"; -import { useOptionsStore } from "../../store/options.store"; -import { useTranslation } from "react-i18next"; +import { ModalState, useSwapStore } from '../../store/swap.store'; +import { fromNano } from '@mytonswap/sdk'; +import { FaArrowRightArrowLeft } from 'react-icons/fa6'; +import SwapKeyValue from '../SwapDetails/SwapKeyValue'; +import formatNumber from '../../utils/formatNum'; +import swap from '../../utils/swap'; +import { FC, useEffect } from 'react'; +import { useOptionsStore } from '../../store/options.store'; +import { useTranslation } from 'react-i18next'; +import Close from '../icons/Close'; type ConfirmationModalProps = { setConfirmModal: (state: ModalState) => void; }; const ConfirmationModal: FC = ({ setConfirmModal }) => { + const { + pay_amount, + pay_token, + bestRoute, + receive_token, + slippage, + setModalState, + } = useSwapStore(); const { t } = useTranslation(); + const handleConfirmClose = () => { setConfirmModal(ModalState.NONE); }; @@ -27,15 +35,6 @@ const ConfirmationModal: FC = ({ setConfirmModal }) => { } }, [tonConnectInstance]); - const { - pay_amount, - pay_token, - bestRoute, - receive_token, - receive_rate, - slippage, - setModalState, - } = useSwapStore(); const handleConfirmSwap = () => { if (tonConnectInstance?.wallet) { swap(tonConnectInstance, bestRoute!); @@ -44,55 +43,63 @@ const ConfirmationModal: FC = ({ setConfirmModal }) => { } }; return ( -
-
- {t("confirm.confirm_title")}{" "} - +
+
+ + {t('confirm.confirm_title')} + {' '} +
-
+
-
+
- {fromNano(pay_amount, pay_token?.decimal)}{" "} + {fromNano(pay_amount, pay_token?.decimal)}{' '} {pay_token?.symbol}
- +
{bestRoute!.pool_data.receive_show!} {receive_token?.symbol}
-
- ≈{" "} + {/*
+ ≈{' '} {formatNumber( Number(bestRoute!.pool_data.receive_show) * receive_rate!.USD, 4 )} $ -
+
*/}
-
+
+ {slippage === 'auto' ? '1% Auto' : slippage + '%'} +
+ } /> +
{formatNumber( bestRoute!.pool_data.minimumReceive_show, 4 @@ -102,44 +109,28 @@ const ConfirmationModal: FC = ({ setConfirmModal }) => { } /> - {/* -
- {bestRoute.selected_pool - .dex === "dedust" - ? "Dedust -" - : "Ston.fi -"} -
*/} - {bestRoute.pool_data.route_view.join(" > ")} +
+ {bestRoute.pool_data.route_view.join(' > ')}
) : ( - "Enter amount" + 'Enter amount' ) } />
-
-
diff --git a/lib/components/SwapButton/Done.tsx b/lib/components/SwapButton/Done.tsx index 09e9717..a292bda 100644 --- a/lib/components/SwapButton/Done.tsx +++ b/lib/components/SwapButton/Done.tsx @@ -1,59 +1,62 @@ -import { FaArrowRightArrowLeft, FaCircleCheck } from "react-icons/fa6"; -import { IoClose } from "react-icons/io5"; -import { ModalState, useSwapStore } from "../../store/swap.store"; -import { fromNano } from "@mytonswap/sdk"; -import formatNumber from "../../utils/formatNum"; -import "./Done.scss"; -import { useTranslation } from "react-i18next"; +import { FaArrowRightArrowLeft, FaCircleCheck } from 'react-icons/fa6'; +import { ModalState, useSwapStore } from '../../store/swap.store'; +import { fromNano } from '@mytonswap/sdk'; +import './Done.scss'; +import { useTranslation } from 'react-i18next'; +import Close from '../icons/Close'; + const Done = () => { const { t } = useTranslation(); - const { bestRoute, pay_amount, pay_token, receive_token, receive_rate } = - useSwapStore(); + const { bestRoute, pay_amount, pay_token, receive_token } = useSwapStore(); const { setModalState } = useSwapStore(); const handleCloseModal = () => { setModalState(ModalState.NONE); }; return ( -
- -
+
+ +
-
{t("transaction.complete")}
-
+
+ {t('transaction.complete')} +
+
-
+
- {fromNano(pay_amount, pay_token?.decimal)}{" "} + {fromNano(pay_amount, pay_token?.decimal)}{' '} {pay_token?.symbol}
- +
{bestRoute!.pool_data.receive_show!} {receive_token?.symbol}
-
- ≈{" "} + {/*
+ ≈{' '} {formatNumber( Number(bestRoute!.pool_data.receive_show) * receive_rate!.USD, 4 )} $ -
+
*/}
); diff --git a/lib/components/SwapButton/ErrorTonConnect.tsx b/lib/components/SwapButton/ErrorTonConnect.tsx index d769ce3..1a2d5ea 100644 --- a/lib/components/SwapButton/ErrorTonConnect.tsx +++ b/lib/components/SwapButton/ErrorTonConnect.tsx @@ -1,7 +1,7 @@ -import { ModalState, useSwapStore } from "../../store/swap.store"; -import { IoClose } from "react-icons/io5"; -import { IoCloseCircle } from "react-icons/io5"; -import "./ErrorTonConnect.scss"; +import { ModalState, useSwapStore } from '../../store/swap.store'; +import { IoCloseCircle } from 'react-icons/io5'; +import Close from '../icons/Close'; + const ErrorTonConnect = () => { const { transactionError, transactionErrorBody, setModalState } = useSwapStore(); @@ -10,15 +10,18 @@ const ErrorTonConnect = () => { transactionError: null, transactionErrorBody: null, }); - // close modal setModalState(ModalState.NONE); }; return ( -
- - -
-

{transactionError}

+
+ +
+ +
+
+

{transactionError}

{transactionErrorBody}

diff --git a/lib/components/SwapButton/Inprogress.tsx b/lib/components/SwapButton/Inprogress.tsx index 089c2cd..d98ae5d 100644 --- a/lib/components/SwapButton/Inprogress.tsx +++ b/lib/components/SwapButton/Inprogress.tsx @@ -3,11 +3,9 @@ import { ModalState, useSwapStore } from '../../store/swap.store'; import { IoClose } from 'react-icons/io5'; import { Dex, fromNano } from '@mytonswap/sdk'; import { FaArrowRightArrowLeft } from 'react-icons/fa6'; -import formatNumber from '../../utils/formatNum'; -import { ImSpinner8 } from 'react-icons/im'; -import './Inprogress.scss'; import { useEventsStore } from '../../store/events.store'; import { useTranslation } from 'react-i18next'; +import Spinner from '../icons/Spinner'; const Inprogress = () => { const { t } = useTranslation(); @@ -78,34 +76,37 @@ const Inprogress = () => { }; return ( -
- -
+
+ +
-
+
{fromNano(pay_amount, pay_token?.decimal)}{' '} {pay_token?.symbol}
- +
{bestRoute!.pool_data.receive_show!} {receive_token?.symbol}
-
+ {/*
≈{' '} {formatNumber( Number(bestRoute!.pool_data.receive_show) * @@ -113,15 +114,17 @@ const Inprogress = () => { 4 )} $ -
+
*/}
-
{t('transaction.pending')}
-

+

+ {t('transaction.pending')} +
+

{t('transaction.action_in_progress')}

-
- +
+
); diff --git a/lib/components/SwapButton/SwapButton.tsx b/lib/components/SwapButton/SwapButton.tsx index 5e06ba5..d586e6d 100644 --- a/lib/components/SwapButton/SwapButton.tsx +++ b/lib/components/SwapButton/SwapButton.tsx @@ -6,7 +6,6 @@ import ConfirmationModal from './ConfirmationModal'; import WaitingForWallet from './WaitingForWallet'; import Inprogress from './Inprogress'; import Done from './Done'; -import './SwapButton.scss'; import { useMediaQuery } from 'usehooks-ts'; import { modalAnimationDesktop, @@ -39,8 +38,8 @@ const SwapButton = () => { const getSwapText = () => { if (isSelectingToken) return ( - - Loading ... + + Loading ... ); if (!tonConnectInstance?.wallet) return t('button_text.connect_wallet'); @@ -97,11 +96,11 @@ const SwapButton = () => { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="modal-container" + className="mts-fixed mts-top-0 mts-left-0 mts-z-[9999999999999999999999999] mts-bg-[rgba(0,0,0,0.5)] mts-w-full mts-h-full mts-overflow-hidden" > { +
+
+
-
+
+
+ {fromNano(pay_amount, pay_token?.decimal)}{' '} + {pay_token?.symbol} +
+
+ +
+
+ {bestRoute!.pool_data.receive_show!} {receive_token?.symbol} +
+ {/*
+ ≈{' '} + {formatNumber( + Number(bestRoute!.pool_data.receive_show) * + receive_rate!.USD, + 4 + )} + $ +
*/} +
+ +
-

{t("confirm.action_in_progress")}

+

{t('confirm.action_in_progress')}

+
+ +
); }; diff --git a/lib/components/SwapCard/Card.tsx b/lib/components/SwapCard/Card.tsx index aabee6e..e214bae 100644 --- a/lib/components/SwapCard/Card.tsx +++ b/lib/components/SwapCard/Card.tsx @@ -1,18 +1,19 @@ -import { ChangeEvent, CSSProperties, FC, useEffect, useState } from "react"; -import { MdKeyboardArrowDown } from "react-icons/md"; -import CardDialog from "./CardDialog"; -import { useSwapStore } from "../../store/swap.store"; -import { Asset, BestRoute, fromNano } from "@mytonswap/sdk"; -import formatNumber from "../../utils/formatNum"; -import CardButton from "./CardButton"; -import { toNano } from "@mytonswap/sdk"; -import { useWalletStore } from "../../store/wallet.store"; -import { TON_ADDR } from "../../constants"; -import "./Card.scss"; -import { useOptionsStore } from "../../store/options.store"; -import { useTranslation } from "react-i18next"; +import { ChangeEvent, CSSProperties, FC, useEffect, useState } from 'react'; +import { MdKeyboardArrowDown } from 'react-icons/md'; +import CardDialog from './CardDialog'; +import { useSwapStore } from '../../store/swap.store'; +import { Asset, BestRoute, fromNano } from '@mytonswap/sdk'; +import formatNumber from '../../utils/formatNum'; +import CardButton from './CardButton'; +import { toNano } from '@mytonswap/sdk'; +import { useWalletStore } from '../../store/wallet.store'; +import { TON_ADDR } from '../../constants'; +import './Card.scss'; +import { useOptionsStore } from '../../store/options.store'; +import { useTranslation } from 'react-i18next'; +import { cn } from '../../utils/cn'; type CardProps = { - type: "pay" | "receive"; + type: 'pay' | 'receive'; }; const Card: FC = ({ type }) => { @@ -20,7 +21,7 @@ const Card: FC = ({ type }) => { const [isSelectVisible, setIsSelectVisible] = useState(false); const [typingTimeout, setTypingTimeout] = useState>(); - const [userInput, setUserInput] = useState(""); + const [userInput, setUserInput] = useState(''); const { setPayToken, pay_token, @@ -37,7 +38,7 @@ const Card: FC = ({ type }) => { const { balance } = useWalletStore(); const { options } = useOptionsStore(); const onTokenSelect = (asset: Asset) => { - if (type === "pay") { + if (type === 'pay') { setPayToken(asset); } else { setReceiveToken(asset); @@ -46,7 +47,7 @@ const Card: FC = ({ type }) => { }; const tokenRate = (() => { - if (type === "pay") { + if (type === 'pay') { return pay_rate?.USD ?? 0; } else { return receive_rate?.USD ?? 0; @@ -54,7 +55,7 @@ const Card: FC = ({ type }) => { })(); const token = (() => { - if (type === "pay") { + if (type === 'pay') { return pay_token; } else { return receive_token; @@ -64,9 +65,9 @@ const Card: FC = ({ type }) => { const isRouteAvailable = bestRoute && bestRoute.pool_data; const value = - type === "pay" + type === 'pay' ? userInput - : (isRouteAvailable && bestRoute?.pool_data.receive_show) ?? 0; + : ((isRouteAvailable && bestRoute?.pool_data.receive_show) ?? 0); const calculatePayRate = (payAmount: bigint, tokenRate: number) => payAmount @@ -88,7 +89,7 @@ const Card: FC = ({ type }) => { : 0; const calculatedRate = - type === "pay" + type === 'pay' ? calculatePayRate(pay_amount, tokenRate) : calculateReceiveRate(bestRoute, tokenRate); @@ -106,11 +107,11 @@ const Card: FC = ({ type }) => { e.preventDefault(); const decimalRegexp = /^\d*(?:\.\d{0,18})?$/; // Allow up to 18 decimal places - let userInput = e.target.value.replace(/,/g, "."); + let userInput = e.target.value.replace(/,/g, '.'); // Handle empty string input - if (userInput === "") { - setUserInput(""); + if (userInput === '') { + setUserInput(''); timeoutSetPayAmount(0n); return; } @@ -118,11 +119,11 @@ const Card: FC = ({ type }) => { // If input matches the decimal pattern if (decimalRegexp.test(userInput)) { // Avoid leading zeros (except for values like "0." or "0.1") - userInput = userInput.replace(/^0+(?=\d)/, ""); + userInput = userInput.replace(/^0+(?=\d)/, ''); // Handle case where input is exactly "0." (valid scenario) - if (userInput === "." || userInput === "0.") { - setUserInput("0."); + if (userInput === '.' || userInput === '0.') { + setUserInput('0.'); } else { setUserInput(userInput); } @@ -137,13 +138,13 @@ const Card: FC = ({ type }) => { }; useEffect(() => { - if (type === "pay") { + if (type === 'pay') { setUserInput(fromNano(pay_amount, pay_token?.decimal)); } }, [pay_amount]); const balanceToken = (() => { - if (type === "pay") { + if (type === 'pay') { return pay_token ? balance.get(pay_token.address) : null; } else { return receive_token ? balance.get(receive_token.address) : null; @@ -164,54 +165,34 @@ const Card: FC = ({ type }) => { setPayAmount(payAmount); }; const isDisabled = (() => { - if (type === "pay" && options.lock_pay_token) return true; - if (type === "receive" && options.lock_receive_token) return true; + if (type === 'pay' && options.lock_pay_token) return true; + if (type === 'receive' && options.lock_receive_token) return true; return false; })(); return ( <> -
-
- - {type === "pay" ? t("you_pay") : t("you_receive")} +
+
+ + {type === 'pay' ? t('you_pay') : t('you_receive')} - {type === "pay" && balanceToken ? ( - - {t("max")} : - - {formatNumber( - +fromNano( - balanceToken.balance, - pay_token!.decimal - ), - 2, - false - )}{" "} - {pay_token?.symbol} - - - ) : ( - - {balanceToken && receive_token && ( - - {formatNumber( - +fromNano( - balanceToken.balance, - receive_token!.decimal - ), - 2, - false - )}{" "} - {receive_token?.symbol} - - )} - - )}
-
-
- {((type === "receive" && !isFindingBestRoute) || - type === "pay") && ( +
+
+ {((type === 'receive' && !isFindingBestRoute) || + type === 'pay') && ( = ({ type }) => { autoCorrect="off" minLength={1} maxLength={14} - value={value ?? ""} + value={value ?? ''} disabled={ - type === "receive" || options.lock_input + type === 'receive' || options.lock_input } onChange={handlePayAmountChange} pattern="^[0-9]*[.,]?[0-9]*$" placeholder="0" - className={`card-input ${type}`} + className={`mts-outline-none mts-bg-transparent mts-h-7 mts-w-full mts-text-black dark:mts-text-white mts-font-bold mts-font-inherit mts-text-lg md:mts-text-2xl ${type}`} data-testid={`swapcard-input-${type}`} /> )} - {type === "receive" && isFindingBestRoute && ( + {type === 'receive' && isFindingBestRoute && (
)} - {((type === "receive" && !isFindingBestRoute) || - type === "pay") && ( - ${calculatedRate} + {((type === 'receive' && !isFindingBestRoute) || + type === 'pay') && ( + + {calculatedRate} $ + )} - {type === "receive" && isFindingBestRoute && ( + {type === 'receive' && isFindingBestRoute && (
)}
-
+
setIsSelectVisible(true)} isLoading={isLoading || !token} >
{token?.symbol}
{!isDisabled && ( -
+
)} + {type === 'pay' ? ( + + + {formatNumber( + +fromNano( + balanceToken?.balance || 0, + pay_token?.decimal || 0 + ), + 2, + false + )}{' '} + {pay_token?.symbol} + + + {t('max')} + + + ) : ( + + {balanceToken && receive_token && ( + + {formatNumber( + +fromNano( + balanceToken.balance, + receive_token!.decimal + ), + 2, + false + )}{' '} + {receive_token?.symbol} + + )} + + )}
diff --git a/lib/components/SwapCard/CardButton.tsx b/lib/components/SwapCard/CardButton.tsx index 7a2b734..2c22831 100644 --- a/lib/components/SwapCard/CardButton.tsx +++ b/lib/components/SwapCard/CardButton.tsx @@ -1,10 +1,11 @@ import { FC, PropsWithChildren } from 'react'; -import clsx from 'clsx'; import { MdKeyboardArrowDown } from 'react-icons/md'; import './CardButton.scss'; import { useOptionsStore } from '../../store/options.store'; import { useTranslation } from 'react-i18next'; import { useSwapStore } from '../../store/swap.store'; +import { cn } from '../../utils/cn'; + type CardButtonProps = { isLoading: boolean; onClick: () => void; @@ -26,6 +27,7 @@ const CardButton: FC = ({ if (type === 'receive' && options.lock_receive_token) return true; return false; })(); + return (
-
- +
+ = ({
{pinnedTokens && ( -
+
{pinnedTokens.map((item) => { return (
{activeTab === TABS.ALL && (
= ({ next={() => onNextPage(page)} scrollableTarget="scroll-div" loader={ -
- +
+
} endMessage={ filteredAssets.length === 0 ? (
{t( 'token_notfound' )} - + {t( 'not_found_desc' )} @@ -412,7 +417,7 @@ const CardDialog: FC = ({
) : (
{t( @@ -444,37 +449,38 @@ const CardDialog: FC = ({ )} {promptForCommunity && ( -
+
{contractCommunity && ( <> -
-
- -

+
+
+
+ +
+

{t( 'trade_warning.trade_title' )}

-

+

{t( 'trade_warning.trade_description' )}

+
{contractCommunity && ( {}} type={type} /> )}

)} -
+
diff --git a/lib/components/SwapCard/FavList.tsx b/lib/components/SwapCard/FavList.tsx index 71dbe2b..8172f97 100644 --- a/lib/components/SwapCard/FavList.tsx +++ b/lib/components/SwapCard/FavList.tsx @@ -1,13 +1,13 @@ -import { CSSProperties, FC, useEffect, useState } from "react"; -import { useFavoriteStore } from "../../store/favtorite.store"; -import { useSwapStore } from "../../store/swap.store"; -import { Asset } from "@mytonswap/sdk"; -import Token from "./Token"; -import { CgSpinnerTwo } from "react-icons/cg"; +import { CSSProperties, FC, useEffect, useState } from 'react'; +import { useFavoriteStore } from '../../store/favtorite.store'; +import { useSwapStore } from '../../store/swap.store'; +import { Asset } from '@mytonswap/sdk'; +import Token from './Token'; +import { CgSpinnerTwo } from 'react-icons/cg'; type FavListProps = { onTokenSelect: (asset: Asset) => void; - type: "pay" | "receive"; + type: 'pay' | 'receive'; }; const FavList: FC = ({ onTokenSelect, type }) => { @@ -32,10 +32,10 @@ const FavList: FC = ({ onTokenSelect, type }) => { }, []); return (
= ({ onTokenSelect, type }) => { /> ))} {isLoading && ( -
- +
+
)} {isError && ( -
+
Something went wrong...
)} {!isLoading && !isError && favItems?.length === 0 && ( -
-
No favorite tokens
+
+
+ No favorite tokens +
)}
diff --git a/lib/components/SwapCard/SwapCard.tsx b/lib/components/SwapCard/SwapCard.tsx index 4b9c5d3..f6c9311 100644 --- a/lib/components/SwapCard/SwapCard.tsx +++ b/lib/components/SwapCard/SwapCard.tsx @@ -1,9 +1,8 @@ -import { useSwapStore } from "../../store/swap.store"; -import Card from "./Card"; -import { LuArrowDownUp } from "react-icons/lu"; +import { useSwapStore } from '../../store/swap.store'; +import Card from './Card'; +import { LuArrowDownUp } from 'react-icons/lu'; -import "./SwapCard.scss"; -import { useOptionsStore } from "../../store/options.store"; +import { useOptionsStore } from '../../store/options.store'; const SwapCard = () => { const { changeDirection } = useSwapStore(); const { options } = useOptionsStore(); @@ -13,20 +12,22 @@ const SwapCard = () => { const isDisabled = options.lock_pay_token || options.lock_receive_token; return ( -
+
-
+ {shouldShowChangeDirection && ( - +
+ +
)}
diff --git a/lib/components/SwapCard/Token.tsx b/lib/components/SwapCard/Token.tsx index 95d52c4..ae5761b 100644 --- a/lib/components/SwapCard/Token.tsx +++ b/lib/components/SwapCard/Token.tsx @@ -3,17 +3,17 @@ import { useWalletStore } from '../../store/wallet.store'; import { FC } from 'react'; import formatNumber from '../../utils/formatNum'; import './Token.scss'; -import { RiExternalLinkLine } from 'react-icons/ri'; -import { PiStarBold } from 'react-icons/pi'; - -import { PiStarFill } from 'react-icons/pi'; - +import { PiStarBold, PiStarFill } from 'react-icons/pi'; import { TokenTon } from '../icons/TokenTon'; import { TON_ADDR } from '../../constants'; import { toFixedDecimal } from '../../utils/toFixedDecimals'; import { useFavoriteStore } from '../../store/favtorite.store'; import clsx from 'clsx'; import { useSwapStore } from '../../store/swap.store'; +import { cn } from '../../utils/cn'; +import Link from '../icons/Link'; +import { useTranslation } from 'react-i18next'; + type TokenProps = { asset: Asset; onTokenSelect: (asset: Asset) => void; @@ -21,6 +21,7 @@ type TokenProps = { }; const Token: FC = ({ asset, onTokenSelect, type }) => { + const { t } = useTranslation(); const { balance } = useWalletStore(); const { pay_token, receive_token } = useSwapStore(); const { isFav, addToFav, removeFromFav } = useFavoriteStore(); @@ -41,39 +42,51 @@ const Token: FC = ({ asset, onTokenSelect, type }) => { ? pay_token?.address === asset.address : receive_token?.address === asset.address || pay_token?.address === asset.address; + return ( -
onTokenSelect(asset)} - className={clsx('token-container', isSelected && 'selected')} + className={clsx( + 'mts-flex mts-items-center mts-cursor-pointer mts-mt-1 mts-rounded-lg mts-w-full mts-h-12 md:mts-h-14', + isSelected && 'mts-opacity-50 mts-cursor-auto' + )} data-testid={asset.address} > -
+
-
-
-
+
+ -
-
- {asset.name} +
+
+ + {asset.name} + {asset.address !== TON_ADDR && ( - - | {asset.liquidity_text} + + | {asset.liquidity_text ?? 0} )}
@@ -81,7 +94,13 @@ const Token: FC = ({ asset, onTokenSelect, type }) => {
-
+
{isTokenFav ? ( { @@ -100,7 +119,7 @@ const Token: FC = ({ asset, onTokenSelect, type }) => {
-
+ ); }; diff --git a/lib/components/SwapDetails/SwapDetails.tsx b/lib/components/SwapDetails/SwapDetails.tsx index 6e25f22..d14ba79 100644 --- a/lib/components/SwapDetails/SwapDetails.tsx +++ b/lib/components/SwapDetails/SwapDetails.tsx @@ -6,7 +6,6 @@ import { AnimatePresence, motion } from 'framer-motion'; import { useSwapStore } from '../../store/swap.store'; import formatNumber from '../../utils/formatNum'; import { CgSpinnerTwo } from 'react-icons/cg'; -import './SwapDetails.scss'; import { useTranslation } from 'react-i18next'; import { useMeasure } from '@uidotdev/usehooks'; import { BsArrowRightShort, BsChevronRight } from 'react-icons/bs'; @@ -19,15 +18,15 @@ const SwapDetails = () => { const [ref, { height }] = useMeasure(); return ( { setIsOpen((prev) => !prev); }} data-testid="swap-details" > -
+
{onePayRoute && onePayRoute.pool_data && !isFindingBestRoute ? ( -
+
1 {onePayRoute.pool_data.route_view[0]} ≈{' '} {formatNumber(onePayRoute.pool_data.receive_show, 4)}{' '} { @@ -37,14 +36,17 @@ const SwapDetails = () => { }
) : ( -
- +
+ {t('fetching_best_route')}
)}
@@ -58,13 +60,16 @@ const SwapDetails = () => { height: height ?? 0, }} exit={{ opacity: 0, height: 0 }} - className="detail-accordion-content" + className="mts-flex mts-flex-col mts-gap-1 mts-w-full mts-overflow-hidden mts-bg-dark-100 dark:mts-bg-dark-900 dark:mts-border-dark-800 mts-rounded-lg mts-border-dark-200 mts-border-[1px]" > -
+
+
{slippage === 'auto' ? '1' : slippage}%{' '} {slippage === 'auto' ? t('auto') : ''}
@@ -99,13 +104,13 @@ const SwapDetails = () => { keyText={t('route')} value={ bestRoute ? ( -
+
{ const RouteView: FC<{ routes: string[] }> = ({ routes }) => { return ( - + {routes.map((route, idx) => ( <> {route}{' '} {idx !== routes.length - 1 && ( - + )} ))} diff --git a/lib/components/SwapDetails/SwapKeyValue.tsx b/lib/components/SwapDetails/SwapKeyValue.tsx index 8e7122c..17a4c3a 100644 --- a/lib/components/SwapDetails/SwapKeyValue.tsx +++ b/lib/components/SwapDetails/SwapKeyValue.tsx @@ -1,5 +1,4 @@ -import { FC, ReactNode } from "react"; -import "./SwapKeyValue.scss"; +import { FC, ReactNode } from 'react'; type SwapKeyValueProps = { keyText: ReactNode; value: ReactNode; @@ -7,9 +6,13 @@ type SwapKeyValueProps = { const SwapKeyValue: FC = ({ keyText, value }) => { return ( -
-
{keyText}
-
{value}
+
+
+ {keyText} +
+
+ {value} +
); }; diff --git a/lib/components/WalletProfile/WalletProfile.tsx b/lib/components/WalletProfile/WalletProfile.tsx index 9b1b27a..50a453b 100644 --- a/lib/components/WalletProfile/WalletProfile.tsx +++ b/lib/components/WalletProfile/WalletProfile.tsx @@ -1,16 +1,18 @@ -import { FC, useEffect, useRef, useState } from "react"; -import { useWalletStore } from "../../store/wallet.store"; -import { ConnectedWallet, TonConnectUI } from "@tonconnect/ui-react"; -import shortAddress from "../../utils/shortAddress"; +import { FC, useEffect, useRef, useState } from 'react'; +import { useWalletStore } from '../../store/wallet.store'; +import { ConnectedWallet, TonConnectUI } from '@tonconnect/ui-react'; +import shortAddress from '../../utils/shortAddress'; -import "./WalletProfile.scss"; -import "../Header/SettingPopover.scss"; -import "../Swap/Swap.scss"; +import './WalletProfile.scss'; +import '../Header/SettingPopover.scss'; +import '../Swap/Swap.scss'; -import { AnimatePresence, motion } from "framer-motion"; -import Wallet from "../Header/Wallet"; -import { useOnClickOutside } from "usehooks-ts"; -import { popOverVariationsKeyValue } from "../../constants"; +import { AnimatePresence, motion } from 'framer-motion'; +import Wallet from '../Header/Wallet'; +import { useOnClickOutside } from 'usehooks-ts'; +import { popOverVariationsKeyValue } from '../../constants'; +import { useTranslation } from 'react-i18next'; +import { cn } from '../../utils/cn'; export type WalletProfileProps = { tonConnectInstance: TonConnectUI; position?: keyof typeof popOverVariationsKeyValue; @@ -26,10 +28,10 @@ export type WalletProfileProps = { export const WalletProfile: FC = ({ tonConnectInstance, - position = "top-right", + position = 'top-right', }) => { const { setWallet, disconnect, wallet } = useWalletStore(); - + const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); const ref = useRef(null); const buttonRef = useRef(null); @@ -66,15 +68,18 @@ export const WalletProfile: FC = ({ const popOverAnimationVariation = popOverVariationsKeyValue[position]; return ( -
+
{wallet - ? shortAddress(wallet.account.address, "mainnet", 4) - : "Connect Wallet"} + ? shortAddress(wallet.account.address, 'mainnet', 4) + : t('button_text.connect_wallet')}
@@ -83,11 +88,11 @@ export const WalletProfile: FC = ({ initial={popOverAnimationVariation.initial} exit={popOverAnimationVariation.exit} animate={popOverAnimationVariation.animate} - transition={{ ease: "easeOut", duration: 0.15 }} + transition={{ ease: 'easeOut', duration: 0.15 }} ref={ref} - className="wallet-popover" + className="mts-absolute mts-shadow-lg mts-rounded-xl mts-bg-modal-background-color mts-p-1 mts-min-w-[15.625rem] mts-w-full mts-h-fit mts-text-text-black-color" > - + )} diff --git a/lib/components/icons/Close.tsx b/lib/components/icons/Close.tsx new file mode 100644 index 0000000..296377c --- /dev/null +++ b/lib/components/icons/Close.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; + +type Props = { + className?: string; +}; + +const Close: FC = ({ className }) => { + return ( + + + + + + + ); +}; + +export default Close; diff --git a/lib/components/icons/Copy.tsx b/lib/components/icons/Copy.tsx new file mode 100644 index 0000000..3cc00ac --- /dev/null +++ b/lib/components/icons/Copy.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; + +type Props = { + className?: string; +}; + +const Copy: FC = ({ className }) => { + return ( + + + + + + + ); +}; + +export default Copy; diff --git a/lib/components/icons/Link.tsx b/lib/components/icons/Link.tsx new file mode 100644 index 0000000..f3db6ea --- /dev/null +++ b/lib/components/icons/Link.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; + +type Props = { + className?: string; +}; + +const Link: FC = ({ className }) => { + return ( + + + + + + + ); +}; + +export default Link; diff --git a/lib/components/icons/Menu.tsx b/lib/components/icons/Menu.tsx new file mode 100644 index 0000000..87b0c02 --- /dev/null +++ b/lib/components/icons/Menu.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; + +type Props = { + className?: string; +}; + +const Menu: FC = ({ className }) => { + return ( + + + + + + + + ); +}; + +export default Menu; diff --git a/lib/components/icons/Refresh.tsx b/lib/components/icons/Refresh.tsx new file mode 100644 index 0000000..3afc08d --- /dev/null +++ b/lib/components/icons/Refresh.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react'; + +type Props = { + className?: string; +}; +const Refresh: FC = ({ className }) => { + return ( + + + + + ); +}; + +export default Refresh; diff --git a/lib/components/icons/Search.tsx b/lib/components/icons/Search.tsx new file mode 100644 index 0000000..5012812 --- /dev/null +++ b/lib/components/icons/Search.tsx @@ -0,0 +1,22 @@ +type Props = { + className?: string; +}; + +const Search = ({ className }: Props) => { + return ( + + + + + + + ); +}; + +export default Search; diff --git a/lib/components/icons/Spinner.tsx b/lib/components/icons/Spinner.tsx new file mode 100644 index 0000000..4c1f0ce --- /dev/null +++ b/lib/components/icons/Spinner.tsx @@ -0,0 +1,27 @@ +type Props = { + className?: string; +}; + +const Spinner = ({ className }: Props) => { + return ( + + + + + + + ); +}; + +export default Spinner; diff --git a/lib/components/icons/Warning.tsx b/lib/components/icons/Warning.tsx new file mode 100644 index 0000000..05f5b71 --- /dev/null +++ b/lib/components/icons/Warning.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; + +type WarningProps = { + className?: string; +}; + +const Warning: FC = ({ className }) => { + return ( + + + + + + + ); +}; + +export default Warning; diff --git a/lib/global.css b/lib/global.css index f8b272e..920ddfa 100644 --- a/lib/global.css +++ b/lib/global.css @@ -1,4 +1,35 @@ -@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap'); +/* @import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap'); */ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --mts-primary-50: 233 251 240; + --mts-primary-100: 207 247 222; + --mts-primary-200: 159 239 188; + --mts-primary-300: 111 230 155; + --mts-primary-400: 64 222 122; + --mts-primary-500: 34 197 94; + --mts-primary-600: 27 157 75; + --mts-primary-700: 20 117 56; + --mts-primary-800: 13 78 37; + --mts-primary-900: 7 39 19; + --mts-primary-950: 4 22 10; + + --mts-dark-50: 242 242 243; + --mts-dark-100: 226 226 228; + --mts-dark-200: 199 199 204; + --mts-dark-300: 170 170 177; + --mts-dark-400: 141 141 150; + --mts-dark-500: 113 113 123; + --mts-dark-600: 90 90 98; + --mts-dark-700: 68 68 74; + --mts-dark-800: 46 46 50; + --mts-dark-900: 22 22 24; + --mts-dark-950: 12 12 13; +} .mts-default { --border-color: #f4f4f5; @@ -67,6 +98,12 @@ --skeleton-shine-color: #585959; } +* { + font-family: 'IBM Plex Mono', sans-serif !important; + /* font-family: 'Fira Mono', sans-serif !important; */ + -webkit-tap-highlight-color: transparent; +} + input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { -webkit-appearance: none; @@ -141,7 +178,7 @@ input[type='number']::-webkit-outer-spin-button { transition: color 0.25s ease-in-out; width: 100%; height: auto; - color: rgb(108, 108, 108); + color: #919191; } .refresh_icon { @@ -151,11 +188,11 @@ input[type='number']::-webkit-outer-spin-button { transition: color 0.25s ease-in-out; width: 100%; height: auto; - color: #6c6c6c; + color: #919191; } .icon_overlay.overlay { - color: #22c55e; + color: rgb(var(--mts-primary-500)); } .active { @@ -221,7 +258,6 @@ input[type='number']::-webkit-outer-spin-button { 50% -50%, 150% 50% ); - color: #f2f2f2; } to { @@ -233,7 +269,6 @@ input[type='number']::-webkit-outer-spin-button { 50% -50%, 150% 50% ); - color: #6c6c6c; } } diff --git a/lib/i18n/langs/en.json b/lib/i18n/langs/en.json index 351df14..fb5b0ba 100644 --- a/lib/i18n/langs/en.json +++ b/lib/i18n/langs/en.json @@ -2,7 +2,7 @@ "swap": "Swap", "max_slippage": "Max Slippage", "auto": "Auto", - "search": "Search ...", + "search": "Search", "not_found_desc": "Double-check your request and try again", "no_more_tokens": "No more tokens", "service_provided": "Service provided by", diff --git a/lib/store/options.store.ts b/lib/store/options.store.ts index 0b46ef9..b09dddf 100644 --- a/lib/store/options.store.ts +++ b/lib/store/options.store.ts @@ -5,6 +5,7 @@ import { useSwapStore } from './swap.store'; import { MyTonSwapClient } from '@mytonswap/sdk'; import { WIDGET_VERSION } from '../constants'; import { useWalletStore } from './wallet.store'; +import { setupColors } from '../utils/color/setupColors'; export type SwapOptions = { default_pay_token?: string; @@ -24,6 +25,8 @@ export type SwapOptions = { | 'omniston'; layout_direction?: 'ltr' | 'rtl'; ui_preferences?: { + primary_color?: string; + dark_color?: string; disable_provided_text?: boolean; show_swap_details?: boolean; show_settings_wallet?: boolean; @@ -52,6 +55,7 @@ export const useOptionsStore = create( options: { liquidity_provider: 'mytonswap', ui_preferences: { + primary_color: '#22C55E', disable_provided_text: false, disable_token_select_pay: false, disable_token_select_receive: false, @@ -68,6 +72,12 @@ export const useOptionsStore = create( setOptions: (option) => { const { options, userOptions } = get(); if (JSON.stringify(option) === JSON.stringify(userOptions)) return; + if (option.ui_preferences?.primary_color) { + setupColors( + option.ui_preferences.primary_color, + option.ui_preferences.dark_color + ); + } const newSchema = defaultsDeep( option, options diff --git a/lib/utils/cn.ts b/lib/utils/cn.ts index e644794..ac834e0 100644 --- a/lib/utils/cn.ts +++ b/lib/utils/cn.ts @@ -1,5 +1,9 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx } from 'clsx'; +import { extendTailwindMerge } from 'tailwind-merge'; + +const twMerge = extendTailwindMerge({ + prefix: 'mts-', +}); export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); diff --git a/lib/utils/color/constants.ts b/lib/utils/color/constants.ts new file mode 100644 index 0000000..5d5cc3f --- /dev/null +++ b/lib/utils/color/constants.ts @@ -0,0 +1,46 @@ +import type { Mode, PaletteConfig } from './types'; + +export const DEFAULT_STOP = 500; +export const DEFAULT_STOPS = [ + 0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, 1000, +]; + +export const MODES: Mode[] = [`hex`, `p-3`, 'oklch']; +export const DEFAULT_MODE = MODES[0]; + +export const DEFAULT_PALETTE_CONFIG: PaletteConfig = { + id: ``, + name: ``, + value: ``, + valueStop: DEFAULT_STOP, + swatches: [], + h: 0, + s: 0, + lMin: 0, + lMax: 100, + useLightness: true, + mode: MODES[0], +}; + +export const RANDOM_PALETTES = [ + { + name: `blue`, + value: `3B82F6`, + }, + { + name: `red`, + value: `EF4444`, + }, + { + name: `green`, + value: `22C55E`, + }, + { + name: `purple`, + value: `A855F7`, + }, + { + name: `brand`, + value: `2522FC`, + }, +]; diff --git a/lib/utils/color/createSwatches.ts b/lib/utils/color/createSwatches.ts new file mode 100644 index 0000000..30e07e3 --- /dev/null +++ b/lib/utils/color/createSwatches.ts @@ -0,0 +1,79 @@ +import { DEFAULT_PALETTE_CONFIG } from './constants'; +import { + clamp, + hexToHSL, + HSLToHex, + lightnessFromHSLum, + luminanceFromHex, + unsignedModulo, +} from './helpers'; +import { + createDistributionValues, + createHueScale, + createSaturationScale, +} from './scales'; +import type { PaletteConfig } from './types'; + +export function createSwatches(palette: PaletteConfig) { + const { value, valueStop } = palette; + + // Tweaks may be passed in, otherwise use defaults + const useLightness = + palette.useLightness ?? DEFAULT_PALETTE_CONFIG.useLightness; + const h = palette.h ?? DEFAULT_PALETTE_CONFIG.h; + const s = palette.s ?? DEFAULT_PALETTE_CONFIG.s; + const lMin = palette.lMin ?? DEFAULT_PALETTE_CONFIG.lMin; + const lMax = palette.lMax ?? DEFAULT_PALETTE_CONFIG.lMax; + + // Create hue and saturation scales based on tweaks + const hueScale = createHueScale(h, valueStop); + const saturationScale = createSaturationScale(s, valueStop); + + // Get the base hex's H/S/L values + const { h: valueH, s: valueS, l: valueL } = hexToHSL(value); + + // Create lightness scales based on tweak + lightness/luminance of current value + const lightnessValue = useLightness ? valueL : luminanceFromHex(value); + const distributionScale = createDistributionValues( + lMin, + lMax, + lightnessValue, + valueStop + ); + + const swatches = hueScale.map(({ stop }, stopIndex) => { + const newH = unsignedModulo(valueH + hueScale[stopIndex].tweak, 360); + const newS = clamp(valueS + saturationScale[stopIndex].tweak, 0, 100); + let newL = useLightness + ? distributionScale[stopIndex].tweak + : lightnessFromHSLum( + newH, + newS, + distributionScale[stopIndex].tweak + ); + newL = clamp(newL, 0, 100); + + const newHex = HSLToHex(newH, newS, newL); + + return { + stop, + // Sometimes the initial value is changed slightly during conversion, + // overriding that with the original value + hex: + stop === valueStop + ? `#${value.toUpperCase()}` + : newHex.toUpperCase(), + // Used in graphs + h: newH, + hScale: + ((unsignedModulo(hueScale[stopIndex].tweak + 180, 360) - 180) / + 180) * + 50, + s: newS, + sScale: newS - 50, + l: newL, + }; + }); + + return swatches; +} diff --git a/lib/utils/color/helpers.ts b/lib/utils/color/helpers.ts new file mode 100644 index 0000000..9ab4a2e --- /dev/null +++ b/lib/utils/color/helpers.ts @@ -0,0 +1,229 @@ +import { DEFAULT_PALETTE_CONFIG } from './constants'; + +export function luminanceFromRGB(r: number, g: number, b: number) { + // Formula from WCAG 2.0 + const [R, G, B] = [r, g, b].map(function (c) { + c /= 255; // to 0-1 range + return c < 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); + }); + return 21.26 * R + 71.52 * G + 7.22 * B; +} + +export function luminanceFromHex(H: string) { + const values: [number, number, number] = Object.values(hexToRGB(H)).map( + (item) => Number(item) + ) as [number, number, number]; + return round(luminanceFromRGB(...values), 2); +} + +// TODO: Even out this function, luminance values aren't linear/good +export function lightnessFromHSLum(H: number, S: number, Lum: number) { + const vals: { [key: number]: number } = {}; + for (let L = 99; L >= 0; L--) { + const values: [number, number, number] = Object.values( + HSLtoRGB(H, S, L) + ) as [number, number, number]; + vals[L] = Math.abs(Lum - luminanceFromRGB(...values)); + } + + // Run through all these and find the closest to 0 + let lowestDiff = 100; + let newL = 100; + for (let i = Object.keys(vals).length - 1; i >= 0; i--) { + if (vals[i] < lowestDiff) { + newL = i; + lowestDiff = vals[i]; + } + } + + return newL; +} + +export function hexToRGB(H: string) { + if (H.length === 6 && !H.startsWith(`#`)) { + H = `#${H}`; + } + + let r = `0`; + let g = `0`; + let b = `0`; + if (H.length === 4) { + r = `0x${H[1]}${H[1]}`; + g = `0x${H[2]}${H[2]}`; + b = `0x${H[3]}${H[3]}`; + } else if (H.length === 7) { + r = `0x${H[1]}${H[2]}`; + g = `0x${H[3]}${H[4]}`; + b = `0x${H[5]}${H[6]}`; + } + + return { r, g, b }; +} + +export function hexToHSL(H: string) { + if (H.length === 6 && !H.startsWith(`#`)) { + H = `#${H}`; + } + + // Convert hex to RGB first + const rgbValues = hexToRGB(H); + let { r, g, b } = { + r: parseInt(rgbValues.r), + g: parseInt(rgbValues.g), + b: parseInt(rgbValues.b), + }; + // Then to HSL + r /= 255; + g /= 255; + b /= 255; + const cmin = Math.min(r, g, b); + const cmax = Math.max(r, g, b); + const delta = cmax - cmin; + let h = 0; + let s = 0; + let l = 0; + + if (delta === 0) h = 0; + else if (cmax === r) h = ((g - b) / delta) % 6; + else if (cmax === g) h = (b - r) / delta + 2; + else h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + if (h < 0) h += 360; + + l = (cmax + cmin) / 2; + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + s = +(s * 100).toFixed(1); + l = +(l * 100).toFixed(1); + + // return `hsl(${h},${s}%,${l}%)`; + return { h, s, l }; +} + +export function HSLtoRGB(h: number, s: number, l: number) { + s = clamp(s, 0, 100) / 100; + l = clamp(l, 0, 100) / 100; + + const c = (1 - Math.abs(2 * l - 1)) * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m = l - c / 2; + let r = 0; + let g = 0; + let b = 0; + + if (h >= 0 && h < 60) { + r = c; + g = x; + b = 0; + } else if (h >= 60 && h < 120) { + r = x; + g = c; + b = 0; + } else if (h >= 120 && h < 180) { + r = 0; + g = c; + b = x; + } else if (h >= 180 && h < 240) { + r = 0; + g = x; + b = c; + } else if (h >= 240 && h < 300) { + r = x; + g = 0; + b = c; + } else if (h >= 300 && h < 360) { + r = c; + g = 0; + b = x; + } + + return { + r: Math.round((r + m) * 255), + g: Math.round((g + m) * 255), + b: Math.round((b + m) * 255), + }; +} + +export function HSLToHex(h: number, s: number, l: number) { + const rgbValues = HSLtoRGB(h, s, l); + let { r, g, b } = { + r: '', + g: '', + b: '', + }; + + // Having obtained RGB, convert channels to hex + r = rgbValues.r.toString(16); + g = rgbValues.g.toString(16); + b = rgbValues.b.toString(16); + + // Prepend 0s, if necessary + if (r.length === 1) r = `0${r}`; + if (g.length === 1) g = `0${g}`; + if (b.length === 1) b = `0${b}`; + + return `#${r}${g}${b}`; +} + +export function isHex(value: string) { + const valueHex = + value.length === 6 && !value.startsWith(`#`) ? `#${value}` : value; + + const re = new RegExp(/^#[0-9A-F]{6}$/i); + + return re.test(valueHex.toUpperCase()); +} + +export function isValidName(name: string) { + const re = new RegExp(/^[A-Za-z]{3,24}$/i); + + return re.test(name); +} + +export function round(value: number, precision: number = 0) { + const multiplier = Math.pow(10, precision); + return Math.round(value * multiplier) / multiplier; +} + +export function removeTrailingSlash(s: string) { + return s.endsWith('/') ? s.slice(0, -1) : s; +} + +export function titleCase(s: string) { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +export function arrayObjectDiff( + before: { [key: string]: string | number | boolean }[], + current: { [key: string]: string | number | boolean }[] +) { + const defaultKeys = Object.keys(DEFAULT_PALETTE_CONFIG); + + // TODO: Fix this TS string-key-nonsense + const changedKeys: (string | null)[] = defaultKeys + .map((key) => { + const beforeValues = before + .map((p) => p[key]) + .sort() + .join(); + + const currentValues = current + .map((p) => p[key]) + .sort() + .join(); + + return beforeValues === currentValues ? null : key; + }) + .filter(Boolean); + + return changedKeys; +} + +export function unsignedModulo(x: number, n: number) { + return ((x % n) + n) % n; +} + +export function clamp(x: number, min: number, max: number) { + return Math.min(Math.max(x, min), max); +} diff --git a/lib/utils/color/scales.ts b/lib/utils/color/scales.ts new file mode 100644 index 0000000..809759c --- /dev/null +++ b/lib/utils/color/scales.ts @@ -0,0 +1,90 @@ +import { DEFAULT_STOP, DEFAULT_STOPS } from './constants'; + +export function createSaturationScale( + tweak: number = 0, + stop: number = DEFAULT_STOP +) { + const stops = DEFAULT_STOPS; + const index = stops.indexOf(stop); + + if (index === -1) { + throw new Error(`Invalid key value: ${stop}`); + } + + return stops.map((stop) => { + const diff = Math.abs(stops.indexOf(stop) - index); + const tweakValue = tweak + ? Math.round((diff + 1) * tweak * (1 + diff / 10)) + : 0; + + if (tweakValue > 100) { + return { stop, tweak: 100 }; + } + + return { stop, tweak: tweakValue }; + }); +} + +export function createHueScale(tweak: number = 0, stop: number = DEFAULT_STOP) { + const stops = DEFAULT_STOPS; + const index = stops.indexOf(stop); + + if (index === -1) { + throw new Error(`Invalid parameter value: ${stop}`); + } + + return stops.map((stop) => { + const diff = Math.abs(stops.indexOf(stop) - index); + const tweakValue = tweak ? diff * tweak : 0; + + return { stop, tweak: tweakValue }; + }); +} + +export function createDistributionValues( + min: number = 0, + max: number = 100, + lightness: number, + stop: number = DEFAULT_STOP +) { + const stops = DEFAULT_STOPS; + + // Create known stops + const newValues = [ + { stop: 0, tweak: max }, + { stop, tweak: lightness }, + { stop: 1000, tweak: min }, + ]; + + // Create missing stops + for (let i = 0; i < stops.length; i++) { + const stopValue = stops[i]; + + if (stopValue === 0 || stopValue === 1000 || stopValue === stop) { + continue; + } + + const diff = Math.abs((stopValue - stop) / 100); + const totalDiff = + stopValue < stop + ? Math.abs( + stops.indexOf(stop) - stops.indexOf(DEFAULT_STOPS[0]) + ) - 1 + : Math.abs( + stops.indexOf(stop) - + stops.indexOf(DEFAULT_STOPS[DEFAULT_STOPS.length - 1]) + ) - 1; + const increment = stopValue < stop ? max - lightness : lightness - min; + + const tweak = + stopValue < stop + ? (increment / totalDiff) * diff + lightness + : lightness - (increment / totalDiff) * diff; + + newValues.push({ stop: stopValue, tweak: Math.round(tweak) }); + } + + newValues.sort((a, b) => a.stop - b.stop); + + return newValues; +} diff --git a/lib/utils/color/setupColors.ts b/lib/utils/color/setupColors.ts new file mode 100644 index 0000000..c29e106 --- /dev/null +++ b/lib/utils/color/setupColors.ts @@ -0,0 +1,78 @@ +import { DEFAULT_PALETTE_CONFIG } from './constants'; +import { nanoid } from 'nanoid'; +import { createSwatches } from './createSwatches'; +import Color from 'color'; +export const setupColors = (color_raw: string, dark_color_base?: string) => { + try { + const color = Color(color_raw).hex(); + const pallet = createSwatches({ + ...DEFAULT_PALETTE_CONFIG, + id: nanoid(), + + value: color.replace('#', '').toUpperCase(), + swatches: [], + }).map((item) => item.hex); + + // Remove the first and last item + const modifiedPallet = pallet.slice(1, -1); + + const cssVariableKeys = [ + '--mts-primary-50', + '--mts-primary-100', + '--mts-primary-200', + '--mts-primary-300', + '--mts-primary-400', + '--mts-primary-500', + '--mts-primary-600', + '--mts-primary-700', + '--mts-primary-800', + '--mts-primary-900', + '--mts-primary-950', + ]; + modifiedPallet.map((item, idx) => { + document.documentElement.style.setProperty( + cssVariableKeys[idx], + Color(item).rgb().array().join(' ') + ); + }); + } catch (error) { + console.log(error); + } + + try { + if (!dark_color_base) return; + const darkColor = Color(dark_color_base).hex(); + const darkPallet = createSwatches({ + ...DEFAULT_PALETTE_CONFIG, + id: nanoid(), + + value: darkColor.replace('#', '').toUpperCase(), + valueStop: 950, + swatches: [], + }).map((item) => item.hex); + + // Remove the first and last item + const modifiedDarkPallet = darkPallet.slice(1, -1); + const darkCssVariableKeys = [ + '--mts-dark-50', + '--mts-dark-100', + '--mts-dark-200', + '--mts-dark-300', + '--mts-dark-400', + '--mts-dark-500', + '--mts-dark-600', + '--mts-dark-700', + '--mts-dark-800', + '--mts-dark-900', + '--mts-dark-950', + ]; + modifiedDarkPallet.map((item, idx) => { + document.documentElement.style.setProperty( + darkCssVariableKeys[idx], + Color(item).rgb().array().join(' ') + ); + }); + } catch (error) { + console.log(error); + } +}; diff --git a/lib/utils/color/types.ts b/lib/utils/color/types.ts new file mode 100644 index 0000000..6ebc06d --- /dev/null +++ b/lib/utils/color/types.ts @@ -0,0 +1,25 @@ +export interface SwatchValue { + hex: string; + stop: number; + h: number; + hScale: number; + s: number; + sScale: number; + l: number; +} + +export type Mode = `hex` | `p-3` | `oklch`; + +export interface PaletteConfig { + id: string; + name: string; + value: string; + valueStop: number; + swatches: SwatchValue[]; + useLightness: boolean; + h: number; + s: number; + lMin: number; + lMax: number; + mode: Mode; +} diff --git a/package.json b/package.json index 1ce1a79..29e01c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mytonswap/widget", "description": "MyTonSwap Widget - Easy to use swap widget for React on TON Blockchain", - "version": "2.0.30", + "version": "3.0.0", "type": "module", "author": { "name": "MyTonSwap", @@ -78,6 +78,7 @@ "@storybook/react": "^8.3.6", "@storybook/react-vite": "^8.3.6", "@storybook/test": "^8.3.6", + "@types/color": "^4.2.0", "@types/lodash": "^4.17.10", "@types/node": "^22.7.4", "@types/react": "^18.3.10", @@ -124,10 +125,12 @@ "@uidotdev/usehooks": "^2.4.1", "axios": "^1.7.7", "axios-retry": "^4.5.0", + "color": "^4.2.3", "framer-motion": "^11.11.2", "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.0", "lodash": "^4.17.21", + "nanoid": "^5.0.9", "react-error-boundary": "^4.1.2", "react-hot-toast": "^2.4.1", "react-i18next": "^15.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f00ca4f..b0e6b15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + color: + specifier: ^4.2.3 + version: 4.2.3 framer-motion: specifier: ^11.11.2 version: 11.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -50,6 +53,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + nanoid: + specifier: ^5.0.9 + version: 5.0.9 react: specifier: ^18.3.1 version: 18.3.1 @@ -123,6 +129,9 @@ importers: '@storybook/test': specifier: ^8.3.6 version: 8.3.6(storybook@8.3.6) + '@types/color': + specifier: ^4.2.0 + version: 4.2.0 '@types/lodash': specifier: ^4.17.10 version: 4.17.10 @@ -1392,6 +1401,15 @@ packages: '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/color-convert@2.0.4': + resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==} + + '@types/color-name@1.1.5': + resolution: {integrity: sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==} + + '@types/color@4.2.0': + resolution: {integrity: sha512-6+xrIRImMtGAL2X3qYkd02Mgs+gFGs+WsK0b7VVMaO4mYRISwyTjcqNrO0mNSmYEoq++rSLDB2F5HDNmqfOe+A==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -2017,6 +2035,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -2818,6 +2843,9 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -3214,6 +3242,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.9: + resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -3831,6 +3864,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -5655,6 +5691,16 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 22.7.4 + '@types/color-convert@2.0.4': + dependencies: + '@types/color-name': 1.1.5 + + '@types/color-name@1.1.5': {} + + '@types/color@4.2.0': + dependencies: + '@types/color-convert': 2.0.4 + '@types/connect@3.4.38': dependencies: '@types/node': 22.7.4 @@ -6366,6 +6412,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + colorette@2.0.20: {} combined-stream@1.0.8: @@ -7277,6 +7333,8 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-arrayish@0.3.2: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -7612,6 +7670,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.9: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -8266,6 +8326,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/tailwind.config.js b/tailwind.config.js index e962d13..98a70a2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,15 +2,44 @@ export default { content: [ // refrence the library only - "./lib/**/**/*.{js,ts,jsx,tsx}", + './lib/**/**/*.{js,ts,jsx,tsx}', ], theme: { extend: { gridTemplateColumns: { - swapcard: "1fr auto", + swapcard: '1fr auto', + }, + colors: { + primary: { + 50: 'rgb(var(--mts-primary-50) / )', + 100: 'rgb(var(--mts-primary-100) / )', + 200: 'rgb(var(--mts-primary-200) / )', + 300: 'rgb(var(--mts-primary-300) / )', + 400: 'rgb(var(--mts-primary-400) / )', + 500: 'rgb(var(--mts-primary-500) / )', + 600: 'rgb(var(--mts-primary-600) / )', + 700: 'rgb(var(--mts-primary-700) / )', + 800: 'rgb(var(--mts-primary-800) / )', + 900: 'rgb(var(--mts-primary-900) / )', + 950: 'rgb(var(--mts-primary-950) / )', + }, + dark: { + 50: 'rgb(var(--mts-dark-50) / )', + 100: 'rgb(var(--mts-dark-100) / )', + 200: 'rgb(var(--mts-dark-200) / )', + 300: 'rgb(var(--mts-dark-300) / )', + 400: 'rgb(var(--mts-dark-400) / )', + 500: 'rgb(var(--mts-dark-500) / )', + 600: 'rgb(var(--mts-dark-600) / )', + 700: 'rgb(var(--mts-dark-700) / )', + 800: 'rgb(var(--mts-dark-800) / )', + 900: 'rgb(var(--mts-dark-900) / )', + 950: 'rgb(var(--mts-dark-950) / )', + }, }, }, }, plugins: [], - darkMode: "class", + darkMode: 'class', + prefix: 'mts-', };