diff --git a/app/history/components/HistoryContent.tsx b/app/history/components/HistoryContent.tsx index 2d9acf3c..03e2619d 100644 --- a/app/history/components/HistoryContent.tsx +++ b/app/history/components/HistoryContent.tsx @@ -1,6 +1,7 @@ 'use client'; -import PrimaryButton from '@/components/common/PrimaryButton'; +import Link from 'next/link'; +import { Button } from '@/components/common/Button'; import Header from '@/components/layout/header/Header'; import LoadingScreen from '@/components/Status/LoadingScreen'; import useUserPositions from '@/hooks/useUserPositions'; @@ -28,7 +29,11 @@ export default function HistoryContent({ account }: { account: string }) { )}
- Back to Portfolio + + +
diff --git a/app/home/HomePage.tsx b/app/home/HomePage.tsx index b5644165..84760f62 100644 --- a/app/home/HomePage.tsx +++ b/app/home/HomePage.tsx @@ -1,8 +1,9 @@ 'use client'; import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import { useAccount } from 'wagmi'; -import PrimaryButton from '@/components/common/PrimaryButton'; +import { Button } from '@/components/common/Button'; import Header from '@/components/layout/header/Header'; export default function HomePage() { @@ -105,12 +106,16 @@ export default function HomePage() {
- - Why Monarch - - - Get Started - + + + + + +
diff --git a/app/market/[chainId]/[marketid]/content.tsx b/app/market/[chainId]/[marketid]/content.tsx index 66965adf..d971e9cb 100644 --- a/app/market/[chainId]/[marketid]/content.tsx +++ b/app/market/[chainId]/[marketid]/content.tsx @@ -1,7 +1,6 @@ 'use client'; import { useState, useCallback } from 'react'; -import { Button } from '@nextui-org/button'; import { Card, CardHeader, CardBody } from '@nextui-org/card'; import { Spinner } from '@nextui-org/spinner'; import { ExternalLinkIcon, ChevronLeftIcon } from '@radix-ui/react-icons'; @@ -9,6 +8,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { formatUnits } from 'viem'; +import { Button } from '@/components/common'; import { OracleFeedInfo } from '@/components/FeedInfo/OracleFeedInfo'; import Header from '@/components/layout/header/Header'; import OracleVendorBadge from '@/components/OracleVendorBadge'; @@ -124,22 +124,20 @@ function MarketContent() {
{/* navigation bottons */}
-
diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 2892f5f2..a0940b73 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Tooltip, Button } from '@nextui-org/react'; +import { Tooltip } from '@nextui-org/react'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { FaShieldAlt } from 'react-icons/fa'; import { GoStarFill, GoStar } from 'react-icons/go'; +import { Button } from '@/components/common/Button'; import OracleVendorBadge from '@/components/OracleVendorBadge'; import { formatReadable } from '@/utils/balance'; import { getNetworkImg } from '@/utils/networks'; @@ -159,7 +160,8 @@ export function MarketTableBody({
- +
diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx index 52e19c91..0a6f4f5a 100644 --- a/app/markets/components/marketsTable.tsx +++ b/app/markets/components/marketsTable.tsx @@ -49,7 +49,7 @@ function MarketsTable({ const totalPages = Math.ceil(markets.length / entriesPerPage); return ( -
+
diff --git a/app/positions/components/FromAndToMarkets.tsx b/app/positions/components/FromAndToMarkets.tsx index a54a8f37..ec560a48 100644 --- a/app/positions/components/FromAndToMarkets.tsx +++ b/app/positions/components/FromAndToMarkets.tsx @@ -92,7 +92,7 @@ export function FromAndToMarkets({

Your Market Positions

onFromFilterChange(e.target.value)} className="mb-2" @@ -228,7 +228,7 @@ export function FromAndToMarkets({

Available Markets for Rebalancing

onToFilterChange(e.target.value)} className="mb-2" diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index bcf0be9c..cce4c228 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -7,6 +7,7 @@ import { useParams } from 'next/navigation'; import { FaHistory, FaGift, FaPlus, FaCircle } from 'react-icons/fa'; import { useAccount } from 'wagmi'; import { Avatar } from '@/components/Avatar/Avatar'; +import { Button } from '@/components/common/Button'; import Header from '@/components/layout/header/Header'; import EmptyScreen from '@/components/Status/EmptyScreen'; import LoadingScreen from '@/components/Status/LoadingScreen'; @@ -64,37 +65,30 @@ export default function Positions() {
- - + - - + {isOwner && ( - - + )}
@@ -126,13 +120,11 @@ export default function Positions() { ) : !hasSuppliedMarkets ? (
- - +
) : ( diff --git a/app/positions/components/PositionsSummaryTable.tsx b/app/positions/components/PositionsSummaryTable.tsx index c879c5ae..bdec7514 100644 --- a/app/positions/components/PositionsSummaryTable.tsx +++ b/app/positions/components/PositionsSummaryTable.tsx @@ -1,11 +1,12 @@ import React, { useMemo, useState, useEffect } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from '@nextui-org/react'; +import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@nextui-org/react'; import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { IoRefreshOutline, IoChevronDownOutline } from 'react-icons/io5'; import { toast } from 'react-toastify'; import { useAccount } from 'wagmi'; +import { Button } from '@/components/common/Button'; import { TokenIcon } from '@/components/TokenIcon'; import { formatReadable, formatBalance } from '@/utils/balance'; import { getNetworkImg } from '@/utils/networks'; @@ -230,9 +231,9 @@ export function PositionsSummaryTable({ @@ -249,10 +250,10 @@ export function PositionsSummaryTable({ @@ -366,9 +367,8 @@ export function PositionsSummaryTable({
diff --git a/app/positions/components/RebalanceActionInput.tsx b/app/positions/components/RebalanceActionInput.tsx index aeb189c1..b9028def 100644 --- a/app/positions/components/RebalanceActionInput.tsx +++ b/app/positions/components/RebalanceActionInput.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Button } from '@nextui-org/react'; import { ArrowRightIcon } from '@radix-ui/react-icons'; import Image from 'next/image'; +import { Button } from '@/components/common'; import { ERC20Token } from '@/utils/tokens'; import { GroupedPosition, Market } from '@/utils/types'; import { MarketBadge } from './MarketBadge'; @@ -59,7 +59,8 @@ export function RebalanceActionInput({
diff --git a/app/positions/components/RebalanceCart.tsx b/app/positions/components/RebalanceCart.tsx index 1bbbc631..61a1825a 100644 --- a/app/positions/components/RebalanceCart.tsx +++ b/app/positions/components/RebalanceCart.tsx @@ -1,14 +1,7 @@ import React from 'react'; -import { - Table, - TableHeader, - TableColumn, - TableBody, - TableRow, - TableCell, - Button, -} from '@nextui-org/react'; +import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@nextui-org/react'; import { formatUnits } from 'viem'; +import { Button } from '@/components/common'; import { Market } from '@/utils/types'; import { GroupedPosition, RebalanceAction } from '@/utils/types'; import { MarketBadge } from './MarketBadge'; @@ -71,10 +64,10 @@ export function RebalanceCart({ diff --git a/app/positions/components/RebalanceModal.tsx b/app/positions/components/RebalanceModal.tsx index ad579967..6d35f756 100644 --- a/app/positions/components/RebalanceModal.tsx +++ b/app/positions/components/RebalanceModal.tsx @@ -5,13 +5,13 @@ import { ModalHeader, ModalBody, ModalFooter, - Button, Spinner, } from '@nextui-org/react'; import { GrRefresh } from 'react-icons/gr'; import { toast } from 'react-toastify'; import { parseUnits, formatUnits } from 'viem'; import { useAccount, useSwitchChain } from 'wagmi'; +import { Button } from '@/components/common'; import { useMarkets } from '@/hooks/useMarkets'; import { usePagination } from '@/hooks/usePagination'; import { useRebalance } from '@/hooks/useRebalance'; @@ -268,28 +268,28 @@ export function RebalanceModal({ isDismissable={false} size="5xl" classNames={{ - base: 'min-w-[1250px] z-[1000] p-4', + base: 'min-w-[1250px] z-[1000] p-4 rounded', backdrop: showProcessModal && 'z-[999]', }} > -
+
Rebalance {groupedPosition.loanAsset ?? 'Unknown'} Position {isRefetching && }
- + -
+

Optimize your {groupedPosition.loanAsset} lending strategy by redistributing funds across markets, add "Rebalance" actions to fine-tune your portfolio. @@ -344,18 +344,18 @@ export function RebalanceModal({ diff --git a/app/positions/components/RebalanceProcessModal.tsx b/app/positions/components/RebalanceProcessModal.tsx index ac273426..5385095a 100644 --- a/app/positions/components/RebalanceProcessModal.tsx +++ b/app/positions/components/RebalanceProcessModal.tsx @@ -65,7 +65,7 @@ export function RebalanceProcessModal({

- -
- +
diff --git a/app/rewards/components/UniformProgram.tsx b/app/rewards/components/UniformProgram.tsx index f4557145..3797bf76 100644 --- a/app/rewards/components/UniformProgram.tsx +++ b/app/rewards/components/UniformProgram.tsx @@ -6,6 +6,7 @@ import Image from 'next/image'; import { toast } from 'react-toastify'; import { Address } from 'viem'; import { useAccount, useSwitchChain } from 'wagmi'; +import { Button } from '@/components/common/Button'; import { DistributionResponseType } from '@/hooks/useRewards'; import { useTransactionWithToast } from '@/hooks/useTransactionWithToast'; import { formatReadable, formatBalance } from '@/utils/balance'; @@ -92,7 +93,7 @@ export default function UniformProgram({ Pending Claimed Total - Action + Action {rewardsData.map((reward, index) => ( @@ -157,42 +158,36 @@ export default function UniformProgram({ )}
- -
- -
+ + ))} diff --git a/docs/Styling.md b/docs/Styling.md index 0d1b8746..b71cd446 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -1,11 +1,108 @@ -# Styling and CSS guidelines +# Styling and CSS Guidelines -## Colors +## Core Components -- background: `bg-main` -- card, button: `bg-surface` +Use these shared components instead of raw HTML elements: -## Tailwind-Compatible Component +- `Button`: Import from `@/components/common/Button` for all clickable actions +- `Modal`: For all modal dialogs +- `Card`: For contained content sections +- `Typography`: For text elements -- primary: `text-primary` -- secondary: `text-secondary` +## Component Guidelines + +### Button Component + +```typescript +import { Button } from '@/components/common/Button'; +``` + +#### Button Variants + +- `default`: Standard surface-colored button + + - Use for: Navigation buttons, "Back", "Cancel" actions + - Example: "Back to Markets", "Cancel" + +- `cta`: Primary call-to-action with orange background + + - Use for: Main actions, confirmations, primary flows + - Example: "Launch App", "Execute Rebalance", "Start Lending" + +- `interactive`: Subtle background with strong hover effect (background → primary) + + - Use for: Table row actions, interactive elements + - Example: "Claim", "Supply", "Withdraw" in tables + +- `secondary`: Subtle background (hovered color) without hover transform + + - Use for: Secondary actions, less prominent options + - Example: "Remove" in tables, "Cancel" in modals + +- `ghost`: Most subtle variant with minimal visual impact + - Use for: Tertiary actions, subtle navigation + - Example: "Refresh" buttons, utility actions + +#### Size Guidelines + +- `sm`: Compact buttons (h-8) + + - Use for: Table actions, tight spaces + - Default padding: px-3 py-1.5 + +- `md`: Standard buttons (h-10) + + - Use for: Most general actions + - Default padding: px-4 py-2 + +- `lg`: Large buttons (h-12) + - Use for: Primary CTAs, important actions + - Default padding: px-6 py-3 + +#### Common Classes + +- Font: `font-zen` for consistent typography +- Icons: Use `mr-2` for icon spacing when icons are present +- Transitions: Built into variants, no need to add transition classes + +#### Examples + +```tsx +// Primary CTA + + +// Table Action + + +// Navigation + + +// Secondary Action + + +// Utility Action + +``` + +2. Modals: + + - Always use `rounded-lg` + - Standard padding: `p-6` + - Consistent max-width: `max-w-lg` + +3. Cards: + - Use `rounded-base` + - Consistent padding: `p-4` + - Standard shadow: `shadow-base` diff --git a/package.json b/package.json index 335a3c0b..e0f60157 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "sharp": "^0.33.5", "shikiji": "^0.9.17", "shikiji-core": "^0.9.17", + "tailwind-merge": "^2.5.5", "unified": "^11.0.4", "viem": "2.x", "wagmi": "^2.10.2", diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx deleted file mode 100644 index 99ca7a11..00000000 --- a/src/components/Button/Button.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @jest-environment jsdom - */ -import React from 'react'; -import { SymbolIcon } from '@radix-ui/react-icons'; -import { render, screen, within } from '@testing-library/react'; -import Button from './Button'; - -describe('Button', () => { - it('should render Button', () => { - render( - ); -} diff --git a/src/components/SupplyProcessModal.tsx b/src/components/SupplyProcessModal.tsx index 10d49af1..34345307 100644 --- a/src/components/SupplyProcessModal.tsx +++ b/src/components/SupplyProcessModal.tsx @@ -1,12 +1,9 @@ import React, { useMemo } from 'react'; import { Cross1Icon } from '@radix-ui/react-icons'; import { motion, AnimatePresence } from 'framer-motion'; -import Image from 'next/image'; import { FaCheckCircle, FaCircle } from 'react-icons/fa'; -import { formatUnits } from 'viem'; -import { formatBalance } from '@/utils/balance'; -import { findToken } from '@/utils/tokens'; import { Market } from '@/utils/types'; +import { MarketAmountBlock } from './common/MarketInfoBlock'; type MarketSupply = { market: Market; @@ -103,7 +100,7 @@ export function SupplyProcessModal({ initial={{ scale: 0.95 }} animate={{ scale: 1 }} exit={{ scale: 0.95 }} - className="relative w-full max-w-lg rounded-lg bg-white p-4 shadow-xl dark:bg-gray-900" + className="relative w-full max-w-lg rounded bg-white p-4 shadow-xl dark:bg-gray-900" >
@@ -176,7 +136,7 @@ export function SupplyProcessModal({ return (
span]:opacity-0 [&>svg]:opacity-0 [&>*:not(.loading-spinner)]:opacity-0', + }, + ], +}); + +export type ButtonProps = React.ComponentProps; + +Button.displayName = 'Button'; diff --git a/src/components/common/MarketInfoBlock.tsx b/src/components/common/MarketInfoBlock.tsx new file mode 100644 index 00000000..526b6b3c --- /dev/null +++ b/src/components/common/MarketInfoBlock.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import Image from 'next/image'; +import { formatUnits } from 'viem'; +import { formatBalance } from '@/utils/balance'; +import { findToken } from '@/utils/tokens'; +import { Market } from '@/utils/types'; +import OracleVendorBadge from '../OracleVendorBadge'; + +type MarketAmountBlockProps = { + market: Market; + amount?: bigint; + lltv?: string; + apy?: string; + className?: string; +}; + +export function MarketAmountBlock({ market, amount }: MarketAmountBlockProps): JSX.Element { + const collateralToken = findToken(market.collateralAsset.address, market.morphoBlue.chain.id); + + return ( +
+
+ {collateralToken?.img && ( +
+ +
+ )} +
+
+ {market.collateralAsset.symbol} + + {formatUnits(BigInt(market.lltv), 16)}% LTV + +
+ {amount ? ( + + {formatBalance(amount, market.loanAsset.decimals)} {market.loanAsset.symbol} + + ) : ( + + )} +
+
+
+
{(market.state.supplyApy * 100).toFixed(2)}%
+
Supply APY
+
+
+ ); +} diff --git a/src/components/common/PrimaryButton.tsx b/src/components/common/PrimaryButton.tsx deleted file mode 100644 index 0645c88e..00000000 --- a/src/components/common/PrimaryButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Link from 'next/link'; - -type PrimaryButtonProps = { - href: string; - children: React.ReactNode; - className?: string; - isSecondary?: boolean; -}; - -export default function PrimaryButton({ - href, - children, - className = '', - isSecondary, -}: PrimaryButtonProps) { - return ( - - - - ); -} diff --git a/src/components/common/index.ts b/src/components/common/index.ts new file mode 100644 index 00000000..8b166a86 --- /dev/null +++ b/src/components/common/index.ts @@ -0,0 +1 @@ +export * from './Button'; diff --git a/src/components/supplyModal.tsx b/src/components/supplyModal.tsx index 41756b73..d105d5cc 100644 --- a/src/components/supplyModal.tsx +++ b/src/components/supplyModal.tsx @@ -4,7 +4,7 @@ import { Cross1Icon, ExternalLinkIcon } from '@radix-ui/react-icons'; import Image from 'next/image'; import Link from 'next/link'; import { toast } from 'react-toastify'; -import { Address, encodeFunctionData, formatUnits } from 'viem'; +import { Address, encodeFunctionData } from 'viem'; import { useAccount, useBalance, useSwitchChain } from 'wagmi'; import morphoBundlerAbi from '@/abis/bundlerV2'; import Input from '@/components/Input/Input'; @@ -18,6 +18,8 @@ import { getExplorerURL } from '@/utils/external'; import { getBundlerV2, getIRMTitle, MONARCH_TX_IDENTIFIER } from '@/utils/morpho'; import { findToken } from '@/utils/tokens'; import { Market } from '@/utils/types'; +import { Button } from './common'; +import { MarketAmountBlock } from './common/MarketInfoBlock'; import OracleVendorBadge from './OracleVendorBadge'; import { SupplyProcessModal } from './SupplyProcessModal'; @@ -37,7 +39,6 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element const { address: account, isConnected, chainId } = useAccount(); - const collateralToken = findToken(market.collateralAsset.address, market.morphoBlue.chain.id); const loanToken = findToken(market.loanAsset.address, market.morphoBlue.chain.id); const { switchChain } = useSwitchChain(); @@ -330,18 +331,21 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element {' '} -
+
Supply {loanToken ? loanToken.symbol : market.loanAsset.symbol} {loanToken?.img && }

- {' '} You are supplying {market.loanAsset.symbol} to the following market:{' '}

-
-
+
+ + +
Details
+ +

Market ID:

@@ -349,21 +353,7 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element

-
-

Collateral Token:

-
-

{market.collateralAsset.symbol}

-
- {collateralToken?.img && ( - - )}{' '} -
-
-
-
-

LLTV:

-

{formatUnits(BigInt(market.lltv), 16)} %

-
+

Oracle:

{needSwitchChain ? ( - + ) : (!permit2Authorized && !useEth) || (!usePermit2Setting && !isApproved) ? ( - + ) : ( - + )}
diff --git a/src/components/withdrawModal.tsx b/src/components/withdrawModal.tsx index b868e3f1..bbfe6044 100644 --- a/src/components/withdrawModal.tsx +++ b/src/components/withdrawModal.tsx @@ -7,6 +7,7 @@ import { toast } from 'react-toastify'; import { Address, encodeFunctionData } from 'viem'; import { useAccount, useSwitchChain } from 'wagmi'; import morphoAbi from '@/abis/morpho'; +import { MarketAmountBlock } from '@/components/common/MarketInfoBlock'; import Input from '@/components/Input/Input'; import AccountConnect from '@/components/layout/header/AccountConnect'; import { useTransactionWithToast } from '@/hooks/useTransactionWithToast'; @@ -106,7 +107,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E
-
+
Withdraw {loanToken ? loanToken.symbol : position.market.loanAsset.symbol} {loanToken?.img && }
@@ -127,12 +128,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E

-
-

Market ID:

-

- {position.market.uniqueKey.slice(2, 8)} -

-
+

Available Liquidity:

diff --git a/src/contexts/MarketsContext.tsx b/src/contexts/MarketsContext.tsx index 9483c4dd..89864672 100644 --- a/src/contexts/MarketsContext.tsx +++ b/src/contexts/MarketsContext.tsx @@ -113,15 +113,18 @@ export function MarketsProvider({ children }: MarketsProviderProps) { } catch (_error) { setError(_error); } finally { - setLoading(false); - setIsRefetching(false); + if (isRefetch) { + setIsRefetching(false); + } else { + setLoading(false); + } } }, [liquidatedMarketIds], ); useEffect(() => { - if (!liquidationsLoading) { + if (!liquidationsLoading && markets.length === 0) { fetchMarkets().catch(console.error); } }, [liquidationsLoading, fetchMarkets]); diff --git a/yarn.lock b/yarn.lock index abd89802..ddb43faf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15323,6 +15323,7 @@ __metadata: stylelint-config-idiomatic-order: "npm:^10.0.0" stylelint-config-standard: "npm:^35.0.0" stylelint-order: "npm:^6.0.4" + tailwind-merge: "npm:^2.5.5" tailwindcss: "npm:^3.4.0" ts-jest: "npm:^29.1.1" typescript: "npm:~5.3.3" @@ -18372,6 +18373,13 @@ __metadata: languageName: node linkType: hard +"tailwind-merge@npm:^2.5.5": + version: 2.5.5 + resolution: "tailwind-merge@npm:2.5.5" + checksum: 10c0/32614dd2b4ddd4fab070d5ec569e6da00e2b34269b9ac2f2ff16733cef29a076c8e2210fbfc1904d7983a8fdb6b3e63d18ca117645f21b12ca7bcf8fe3507241 + languageName: node + linkType: hard + "tailwind-variants@npm:^0.1.20": version: 0.1.20 resolution: "tailwind-variants@npm:0.1.20"