diff --git a/app/admin/stats/components/AssetMetricsTable.tsx b/app/admin/stats/components/AssetMetricsTable.tsx index 1ddd279f..0225ed05 100644 --- a/app/admin/stats/components/AssetMetricsTable.tsx +++ b/app/admin/stats/components/AssetMetricsTable.tsx @@ -1,5 +1,6 @@ import { useState, useMemo } from 'react'; import { FiChevronUp, FiChevronDown } from 'react-icons/fi'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { TokenIcon } from '@/components/TokenIcon'; import { formatReadable } from '@/utils/balance'; import { calculateHumanReadableVolumes } from '@/utils/statsDataProcessing'; @@ -25,7 +26,7 @@ type SortableHeaderProps = { function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, onSort }: SortableHeaderProps) { return ( - onSort(sortKeyValue)} style={{ padding: '0.5rem' }} @@ -35,7 +36,7 @@ function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, on {currentSortKey === sortKeyValue && (sortDirection === 'asc' ? : )} - + ); } @@ -92,10 +93,10 @@ export function AssetMetricsTable({ data }: AssetMetricsTableProps) { {processedData.length === 0 ? (
No asset data available
) : ( - - - - +
Asset
+ + + Asset - - - + + + {sortedData.map((asset) => { // Use a determined chainId for display purposes const displayChainId = asset.chainId ?? BASE_CHAIN_ID; return ( - - - - - - - - + + ); })} - -
{asset.assetSymbol ?? 'Unknown'} - + {asset.totalVolumeFormatted ? `${formatReadable(Number(asset.totalVolumeFormatted))} ${asset.assetSymbol}` : '—'} - + {(asset.supplyCount + asset.withdrawCount).toLocaleString()} - + {asset.supplyCount.toLocaleString()} - + {asset.withdrawCount.toLocaleString()} - + {asset.uniqueUsers.toLocaleString()} -
+ + )} diff --git a/app/admin/stats/components/TransactionTableBody.tsx b/app/admin/stats/components/TransactionTableBody.tsx index 01fb35ca..63ee7be9 100644 --- a/app/admin/stats/components/TransactionTableBody.tsx +++ b/app/admin/stats/components/TransactionTableBody.tsx @@ -1,5 +1,6 @@ import Link from 'next/link'; import { formatUnits } from 'viem'; +import { TableBody, TableRow, TableCell } from '@/components/ui/table'; import { AccountIdentity } from '@/components/common/AccountIdentity'; import { TransactionIdentity } from '@/components/common/TransactionIdentity'; import { MarketIdBadge } from '@/components/MarketIdBadge'; @@ -66,17 +67,17 @@ const formatAmount = (amount: string, side: 'Supply' | 'Withdraw', loanAddress: export function TransactionTableBody({ operations, selectedNetwork }: TransactionTableBodyProps) { return ( - + {operations.map((op) => { const marketPath = op.market ? `/market/${selectedNetwork}/${op.market.uniqueKey}` : null; return ( - {/* User Address */} - - + {/* Loan Asset */} - {getTruncatedAssetName(op.loanSymbol)} - + {/* Market */} - — )} - + {/* Side */} - {op.side} - + {/* Amount */} - {formatAmount(op.amount, op.side, op.loanAddress, selectedNetwork)} - + {/* Transaction Hash */} - - + {/* Time */} - {formatTimeAgo(op.timestamp)} - - + + ); })} - + ); } diff --git a/app/admin/stats/components/TransactionsTable.tsx b/app/admin/stats/components/TransactionsTable.tsx index dfa3bc2c..8af052d9 100644 --- a/app/admin/stats/components/TransactionsTable.tsx +++ b/app/admin/stats/components/TransactionsTable.tsx @@ -1,5 +1,6 @@ import { useState, useMemo } from 'react'; import { FiChevronUp, FiChevronDown } from 'react-icons/fi'; +import { Table, TableHeader, TableRow, TableHead } from '@/components/ui/table'; import { TablePagination } from '@/components/common/TablePagination'; import type { SupportedNetworks } from '@/utils/networks'; import type { Transaction } from '@/utils/statsUtils'; @@ -41,7 +42,7 @@ type SortableHeaderProps = { function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, onSort }: SortableHeaderProps) { return ( - onSort(sortKeyValue)} style={{ padding: '0.5rem' }} @@ -51,7 +52,7 @@ function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, on {currentSortKey === sortKeyValue && (sortDirection === 'asc' ? : )} - + ); } @@ -196,13 +197,13 @@ export function TransactionsTable({
No transaction data available
) : ( <> - - - - - - - +
UserLoan AssetMarketSide
+ + + User + Loan Asset + Market + Side - + Tx Hash - - + + -
Tx Hash
+
- - - - - - - - - +
CollateralAmountAllocation -
+ + + Collateral + Amount + Allocation + + + + {sortedItems.map((item) => { const percentage = totalAllocation > 0n ? Number.parseFloat(calculateAllocationPercent(item.allocation, totalAllocation)) : 0; const hasAllocation = item.allocation > 0n; return ( - - - - - - + + ); })} - -
+
{item.collateralSymbol}
-
+ + {hasAllocation ? `${formatAllocationAmount(item.allocation, vaultAssetDecimals)} ${vaultAssetSymbol}` : '-'} - + + {hasAllocation ? `${percentage.toFixed(2)}%` : '-'} - + +
-
+ +
); } diff --git a/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx b/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx index 8dad6722..3cf86c33 100644 --- a/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx @@ -1,3 +1,4 @@ +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { MarketIdentity, MarketIdentityFocus } from '@/components/MarketIdentity'; import { useMarkets } from '@/hooks/useMarkets'; import { useRateLabel } from '@/hooks/useRateLabel'; @@ -29,19 +30,19 @@ export function MarketView({ allocations, totalAllocation, vaultAssetSymbol, vau return (
- - - - - - - - - - - - +
Market{rateLabel}Total SupplyLiquidityAmountAllocation -
+ + + Market + {rateLabel} + Total Supply + Liquidity + Amount + Allocation + + + + {sortedItems.map((item) => { const { market, allocation } = item; const percentage = totalAllocation > 0n ? Number.parseFloat(calculateAllocationPercent(allocation, totalAllocation)) : 0; @@ -54,12 +55,12 @@ export function MarketView({ allocations, totalAllocation, vaultAssetSymbol, vau ); return ( - {/* Market Info Column */} - + {/* APY/APR */} - + {supplyRate}% {/* Total Supply */} - + {totalSupply} {/* Liquidity */} - + {liquidity} {/* Allocation Amount */} - + {/* Allocation Percentage */} - + {/* Pie Chart */} - - + + ); })} - -
+ - {supplyRate}%{totalSupply}{liquidity} + {hasAllocation ? `${formatAllocationAmount(allocation, vaultAssetDecimals)} ${vaultAssetSymbol}` : '-'} - + {hasAllocation ? `${percentage.toFixed(2)}%` : '-'} - +
-
+ +
); } diff --git a/app/autovault/components/VaultListV2.tsx b/app/autovault/components/VaultListV2.tsx index 9bb11fa8..f615a745 100644 --- a/app/autovault/components/VaultListV2.tsx +++ b/app/autovault/components/VaultListV2.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { formatUnits } from 'viem'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/common/Spinner'; import { useTokens } from '@/components/providers/TokenProvider'; @@ -51,17 +52,17 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) {

Your Vaults

- - - - - - - - - - - +
IDAsset{rateLabel}CollateralsAction
+ + + ID + Asset + {rateLabel} + Collaterals + Action + + + {vaults.map((vault) => { const token = findToken(vault.asset, vault.networkId); const networkImg = getNetworkImg(vault.networkId); @@ -71,9 +72,9 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) { .filter((collat) => collat !== undefined); return ( - + {/* ID */} - + {/* Asset */} - + {/* APY/APR */} - + {/* Collaterals */} - + {/* Action */} - - + + ); })} - -
+
{networkImg && ( {vault.address.slice(2, 8)}
-
+
{vault.balance && token ? formatReadable(formatUnits(BigInt(vault.balance), token.decimals)) : '0'} @@ -101,17 +102,17 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) { height={16} />
-
+ {vault.avgApy != null ? `${((isAprDisplay ? convertApyToApr(vault.avgApy) : vault.avgApy) * 100).toFixed(2)}%` : '—'} - + {collaterals.map((tokenAddress) => (
))} -
+
-
+ +
); diff --git a/app/global.css b/app/global.css index 44053dee..a6971053 100644 --- a/app/global.css +++ b/app/global.css @@ -303,35 +303,35 @@ h1 { padding-bottom: 0.5rem; } -.table-header { +/* Table styles - applied automatically to thead and tbody */ +thead { background-color: var(--color-background-secondary); font-size: 0.8em; color: grey; } -.table-header th { - padding-top: 1rem; - padding-bottom: 1rem; - text-align: center; +thead th { + padding: 1rem; + padding-left: 1rem; + padding-right: 1rem; } -.table-body { +tbody { background-color: var(--color-background-secondary); border-left: 2px solid var(--color-background-secondary); } -.table-body td { +tbody td { padding: 1rem; padding-left: 1rem; padding-right: 1rem; - text-align: center; } -.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr) { +tbody tr:not(.no-hover-effect tr) { border-left: 2px solid transparent; } -.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr):hover { +tbody tr:not(.no-hover-effect tr):hover { background-color: var(--palette-bg-hovered); border-left: 2px solid var(--palette-orange); } @@ -341,6 +341,13 @@ h1 { border-left: 2px solid var(--palette-orange) !important; } +/* Compact table variant - less padding for simpler tables */ +.table-body-compact td { + padding: 0.625rem; + padding-left: 0.75rem; + padding-right: 0.75rem; +} + svg { display: block; overflow: visible; diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index 666b6ed6..8a3c3b6e 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -1,12 +1,14 @@ import type React from 'react'; import { useMemo, useState, useRef, useEffect } from 'react'; -import { Chip, Link, Pagination, Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react'; +import { Chip, Link } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import { ChevronDownIcon, TrashIcon } from '@radix-ui/react-icons'; import moment from 'moment'; import Image from 'next/image'; import { RiRobot2Line } from 'react-icons/ri'; import { formatUnits } from 'viem'; import { Badge } from '@/components/common/Badge'; +import { TablePagination } from '@/components/common/TablePagination'; import { TransactionIdentity } from '@/components/common/TransactionIdentity'; import LoadingScreen from '@/components/Status/LoadingScreen'; import { TokenIcon } from '@/components/TokenIcon'; @@ -252,146 +254,152 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab {!isInitialized || loading ? ( ) : ( - 1 ? ( -
- -
- ) : null - } - > - - Asset & Network - Market Details - Action & Amount - Time - Transaction - - - {history - .filter((tx) => tx.data.market !== undefined) - .map((tx, index) => { - // safely cast here because we only fetch txs for unique id in "markets" - const market = allMarkets.find((m) => m.uniqueKey === tx.data.market.uniqueKey) as Market; + <> +
+ + + Asset & Network + Market Details + Action & Amount + Time + Transaction + + + + {history.filter((tx) => tx.data.market !== undefined).length === 0 ? ( + + + No transactions found + + + ) : ( + history + .filter((tx) => tx.data.market !== undefined) + .map((tx, index) => { + // safely cast here because we only fetch txs for unique id in "markets" + const market = allMarkets.find((m) => m.uniqueKey === tx.data.market.uniqueKey) as Market; - const networkImg = getNetworkImg(market.morphoBlue.chain.id); - const networkName = getNetworkName(market.morphoBlue.chain.id); - const sign = tx.type === UserTxTypes.MarketSupply ? '+' : '-'; - const lltv = Number(formatUnits(BigInt(market.lltv), 18)) * 100; + const networkImg = getNetworkImg(market.morphoBlue.chain.id); + const networkName = getNetworkName(market.morphoBlue.chain.id); + const sign = tx.type === UserTxTypes.MarketSupply ? '+' : '-'; + const lltv = Number(formatUnits(BigInt(market.lltv), 18)) * 100; - // Find the rebalancer info for the specific network of the transaction - const networkRebalancerInfo = rebalancerInfos.find((info) => info.network === market.morphoBlue.chain.id); - // Check if the transaction hash exists in the transactions of the found rebalancer info - const isAgent = networkRebalancerInfo?.transactions.some((agentTx) => agentTx.transactionHash === tx.hash); + // Find the rebalancer info for the specific network of the transaction + const networkRebalancerInfo = rebalancerInfos.find((info) => info.network === market.morphoBlue.chain.id); + // Check if the transaction hash exists in the transactions of the found rebalancer info + const isAgent = networkRebalancerInfo?.transactions.some((agentTx) => agentTx.transactionHash === tx.hash); - return ( - - {/* Network & Asset */} - -
-
- - {market.loanAsset.symbol} -
-
- {networkImg && ( - - )} - {networkName} -
-
-
+ return ( + + {/* Network & Asset */} + +
+
+ + {market.loanAsset.symbol} +
+
+ {networkImg && ( + + )} + {networkName} +
+
+
- {/* Market Details */} - -
- - {market.uniqueKey.slice(2, 8)} - -
- - {market.collateralAsset.symbol} -
- - {formatReadable(lltv)}% - -
-
+ {/* Market Details */} + +
+ + {market.uniqueKey.slice(2, 8)} + +
+ + {market.collateralAsset.symbol} +
+ + {formatReadable(lltv)}% + +
+
- {/* Action & Amount */} - -
- {actionTypeToText(tx.type)} - - {sign} - {formatReadable(Number(formatUnits(BigInt(tx.data.assets), market.loanAsset.decimals)))} {market.loanAsset.symbol} - - {isAgent && ( - - - - )} -
-
+ {/* Action & Amount */} + +
+ {actionTypeToText(tx.type)} + + {sign} + {formatReadable(Number(formatUnits(BigInt(tx.data.assets), market.loanAsset.decimals)))}{' '} + {market.loanAsset.symbol} + + {isAgent && ( + + + + )} +
+
- {/* Time */} - -
{moment.unix(tx.timestamp).fromNow()}
-
+ {/* Time */} + +
{moment.unix(tx.timestamp).fromNow()}
+
- {/* Transaction */} - -
- -
-
-
- ); - })} -
-
+ {/* Transaction */} + +
+ +
+
+ + ); + }) + )} + + + {totalPages > 1 && ( + + )} + )} ); diff --git a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx index f536d884..d170542c 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx @@ -1,5 +1,6 @@ import { useState, useMemo } from 'react'; -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react'; +import { Tooltip } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import { FiFilter } from 'react-icons/fi'; import type { Address } from 'viem'; import { formatUnits } from 'viem'; @@ -108,76 +109,83 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen )} - - - ACCOUNT - BORROWED - COLLATERAL - LTV - % OF BORROW - - +
- {borrowersWithLTV.map((borrower) => { - const totalBorrow = BigInt(market.state.borrowAssets); - const borrowerAssets = BigInt(borrower.borrowAssets); - const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0; - const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`; - - return ( - - - + + + ACCOUNT + BORROWED + COLLATERAL + LTV + % OF BORROW + + + + {borrowersWithLTV.length === 0 && !isLoading ? ( + + + No borrowers found for this market - - {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - - - - )} - - - {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))} - {market?.collateralAsset?.symbol && ( - - - - )} - - {borrower.ltv.toFixed(2)}% - {percentDisplay} - ); - })} - -
+ ) : ( + borrowersWithLTV.map((borrower) => { + const totalBorrow = BigInt(market.state.borrowAssets); + const borrowerAssets = BigInt(borrower.borrowAssets); + const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0; + const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`; + + return ( + + + + + +
+ {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + )} +
+
+ +
+ {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))} + {market?.collateralAsset?.symbol && ( + + )} +
+
+ {borrower.ltv.toFixed(2)}% + {percentDisplay} +
+ ); + }) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx index 54f758e7..6ea60051 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react'; +import { Tooltip } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import moment from 'moment'; import { FiFilter } from 'react-icons/fi'; import type { Address } from 'viem'; @@ -92,70 +93,72 @@ export function BorrowsTable({ chainId, market, minAssets, onOpenFiltersModal }: )} - - - ACCOUNT - TYPE - AMOUNT - TIME - - TRANSACTION - - - +
- {borrows.map((borrow) => ( - - - - - - - {borrow.type === 'MarketBorrow' ? 'Borrow' : 'Repay'} - - - - {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - - - - )} - - {moment.unix(borrow.timestamp).fromNow()} - - - + + + ACCOUNT + TYPE + AMOUNT + TIME + TRANSACTION - ))} - -
+ + + {borrows.length === 0 && !isLoading ? ( + + + No borrow activities found for this market + + + ) : ( + borrows.map((borrow) => ( + + + + + + + {borrow.type === 'MarketBorrow' ? 'Borrow' : 'Repay'} + + + +
+ {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + )} +
+
+ {moment.unix(borrow.timestamp).fromNow()} + + + +
+ )) + )} +
+ + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 9b9cf4b1..ca43bdc9 100644 --- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react'; -import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import moment from 'moment'; import { type Address, formatUnits } from 'viem'; import { AccountIdentity } from '@/components/common/AccountIdentity'; @@ -8,6 +8,7 @@ import { TablePagination } from '@/components/common/TablePagination'; import { TransactionIdentity } from '@/components/common/TransactionIdentity'; import { TokenIcon } from '@/components/TokenIcon'; import { useMarketLiquidations } from '@/hooks/useMarketLiquidations'; +import { formatSimple } from '@/utils/balance'; import type { Market, MarketLiquidationTransaction } from '@/utils/types'; type LiquidationsTableProps = { @@ -51,83 +52,53 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) { )} - - - LIQUIDATOR - REPAID ({market?.loanAsset?.symbol ?? 'Loan'}) - SEIZED ({market?.collateralAsset?.symbol ?? 'Collateral'}) - BAD DEBT ({market?.loanAsset?.symbol ?? 'Loan'}) - TIME - - TRANSACTION - - - +
- {paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => { - const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0); - const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x'); - - return ( - - - {isLiquidatorAddress ? ( - - ) : ( - {liquidation.liquidator} - )} - - - {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)} - {market?.loanAsset?.symbol && ( - - - - )} - - - {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)} - {market?.collateralAsset?.symbol && ( - - - - )} + + + LIQUIDATOR + REPAID ({market?.loanAsset?.symbol ?? 'Loan'}) + SEIZED ({market?.collateralAsset?.symbol ?? 'Collateral'}) + BAD DEBT ({market?.loanAsset?.symbol ?? 'Loan'}) + TIME + TRANSACTION + + + + {paginatedLiquidations.length === 0 && !isLoading ? ( + + + No liquidations found for this market - - {hasBadDebt ? ( - <> - {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} - {market?.loanAsset?.symbol && ( - + + ) : ( + paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => { + const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0); + const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x'); + + return ( + + + {isLiquidatorAddress ? ( + + ) : ( + {liquidation.liquidator} + )} + + +
+ {formatSimple(Number(formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + )} +
+
+ +
+ + {formatSimple(Number(formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)))} + {market?.collateralAsset?.symbol && ( + + )} +
+
+ + {hasBadDebt ? ( +
+ {formatSimple(Number(formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + )} +
+ ) : ( +
-
)} - - ) : ( - ' - ' - )} -
- {moment.unix(liquidation.timestamp).fromNow()} - - - -
- ); - })} -
-
+ + {moment.unix(liquidation.timestamp).fromNow()} + + + + + ); + }) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx index d73af9a1..bb12d7ee 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx @@ -1,5 +1,6 @@ import { useState, useMemo } from 'react'; -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react'; +import { Tooltip } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import { FiFilter } from 'react-icons/fi'; import type { Address } from 'viem'; import { formatUnits } from 'viem'; @@ -101,59 +102,66 @@ export function SuppliersTable({ chainId, market, minShares, onOpenFiltersModal )} - - - ACCOUNT - SUPPLIED - % OF SUPPLY - - +
- {suppliersWithAssets.map((supplier) => { - const totalSupply = BigInt(market.state.supplyAssets); - const supplierAssets = BigInt(supplier.supplyAssets); - const percentOfSupply = totalSupply > 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0; - const percentDisplay = percentOfSupply < 0.01 && percentOfSupply > 0 ? '<0.01%' : `${percentOfSupply.toFixed(2)}%`; - - return ( - - - + + + ACCOUNT + SUPPLIED + % OF SUPPLY + + + + {suppliersWithAssets.length === 0 && !isLoading ? ( + + + No suppliers found for this market - - {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - - - - )} - - {percentDisplay} - ); - })} - -
+ ) : ( + suppliersWithAssets.map((supplier) => { + const totalSupply = BigInt(market.state.supplyAssets); + const supplierAssets = BigInt(supplier.supplyAssets); + const percentOfSupply = totalSupply > 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0; + const percentDisplay = percentOfSupply < 0.01 && percentOfSupply > 0 ? '<0.01%' : `${percentOfSupply.toFixed(2)}%`; + + return ( + + + + + +
+ {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + )} +
+
+ {percentDisplay} +
+ ); + }) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx index 12a2acb1..2f8090a7 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react'; +import { Tooltip } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import moment from 'moment'; import { FiFilter } from 'react-icons/fi'; import type { Address } from 'viem'; @@ -87,70 +88,72 @@ export function SuppliesTable({ chainId, market, minAssets, onOpenFiltersModal } )} - - - ACCOUNT - TYPE - AMOUNT - TIME - - TRANSACTION - - - +
- {supplies.map((supply) => ( - - - - - - - {supply.type === 'MarketSupply' ? 'Supply' : 'Withdraw'} - - - - {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - - - - )} - - {moment.unix(supply.timestamp).fromNow()} - - - + + + ACCOUNT + TYPE + AMOUNT + TIME + TRANSACTION - ))} - -
+ + + {supplies.length === 0 && !isLoading ? ( + + + No supply activities found for this market + + + ) : ( + supplies.map((supply) => ( + + + + + + + {supply.type === 'MarketSupply' ? 'Supply' : 'Withdraw'} + + + +
+ {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + )} +
+
+ {moment.unix(supply.timestamp).fromNow()} + + + +
+ )) + )} +
+ + {totalCount > 0 && ( diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 573eb58a..ba7d7c4a 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { GoStarFill, GoStar } from 'react-icons/go'; +import { TableBody, TableRow, TableCell } from '@/components/ui/table'; import { RateFormatted } from '@/components/common/RateFormatted'; import { MarketIdBadge } from '@/components/MarketIdBadge'; import { MarketIndicators } from '@/components/MarketIndicators'; @@ -93,19 +94,19 @@ export function MarketTableBody({ }; return ( - + {currentEntries.map((item, index) => { const collatToShow = item.collateralAsset.symbol.slice(0, 6).concat(item.collateralAsset.symbol.length > 6 ? '...' : ''); const isStared = staredIds.includes(item.uniqueKey); return ( - setExpandedRowId(item.uniqueKey === expandedRowId ? null : item.uniqueKey)} - className={`hover:cursor-pointer ${item.uniqueKey === expandedRowId ? 'table-body-focused ' : ''}'`} + className={`hover:cursor-pointer ${item.uniqueKey === expandedRowId ? 'table-body-focused ' : ''}`} > -

{isStared ? : }

- - + - + - - - + {Number(item.lltv) / 1e16}% - + {columnVisibility.trustedBy && ( - - + )} {columnVisibility.totalSupply && ( )} {columnVisibility.supplyAPY && ( - - + )} {columnVisibility.borrowAPY && ( -

{item.state.borrowApy ? : '—'}

- + )} {columnVisibility.rateAtTarget && ( -

{item.state.apyAtTarget ? : '—'}

- + )} {columnVisibility.utilizationRate && ( -

{`${(item.state.utilization * 100).toFixed(2)}%`}

- + )} - +
- - + - - + @@ -280,12 +281,12 @@ export function MarketTableBody({ isBlacklisted={isBlacklisted} /> - - + + {expandedRowId === item.uniqueKey && ( - - + @@ -301,13 +302,13 @@ export function MarketTableBody({ - - + + )}
); })} - +
); } diff --git a/app/markets/components/MarketTableUtils.tsx b/app/markets/components/MarketTableUtils.tsx index 13de6127..7df7f300 100644 --- a/app/markets/components/MarketTableUtils.tsx +++ b/app/markets/components/MarketTableUtils.tsx @@ -1,4 +1,5 @@ import { ArrowDownIcon, ArrowUpIcon, ExternalLinkIcon } from '@radix-ui/react-icons'; +import { TableHead, TableCell } from '@/components/ui/table'; import { TokenIcon } from '@/components/TokenIcon'; import { formatBalance, formatReadable } from '@/utils/balance'; import { getAssetURL } from '@/utils/external'; @@ -17,7 +18,7 @@ export function HTSortable({ label, sortColumn, titleOnclick, sortDirection, tar const sortingCurrent = sortColumn === targetColumn; return ( - titleOnclick(targetColumn)} style={{ padding: '0.5rem' }} @@ -26,13 +27,13 @@ export function HTSortable({ label, sortColumn, titleOnclick, sortDirection, tar
{label}
{showDirection && (sortingCurrent ? sortDirection === 1 ? : : null)} - + ); } export function TDAsset({ asset, chainId, symbol, dataLabel }: { asset: string; chainId: number; symbol: string; dataLabel?: string }) { return ( - - + ); } @@ -75,13 +76,13 @@ export function TDTotalSupplyOrBorrow({ symbol: string; }) { return ( -

${`${formatReadable(Number(assetsUSD))} `}

{`${formatReadable(formatBalance(assets, decimals))} ${symbol}`}

- + ); } diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx index 3cfb3731..33538880 100644 --- a/app/markets/components/marketsTable.tsx +++ b/app/markets/components/marketsTable.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react'; import { FaRegStar, FaStar } from 'react-icons/fa'; +import { Table, TableHeader, TableRow, TableHead } from '@/components/ui/table'; import { TablePagination } from '@/components/common/TablePagination'; import type { TrustedVault } from '@/constants/vaults/known_vaults'; import { useRateLabel } from '@/hooks/useRateLabel'; @@ -68,15 +69,17 @@ function MarketsTable({ const totalPages = Math.ceil(markets.length / entriesPerPage); const containerClassName = ['flex flex-col gap-2 pb-4', className].filter((value): value is string => Boolean(value)).join(' '); - const tableWrapperClassName = ['overflow-x-auto', wrapperClassName].filter((value): value is string => Boolean(value)).join(' '); - const tableClassNames = ['responsive rounded-md font-zen', tableClassName].filter((value): value is string => Boolean(value)).join(' '); + const tableWrapperClassName = ['bg-surface shadow-sm rounded overflow-hidden', wrapperClassName] + .filter((value): value is string => Boolean(value)) + .join(' '); + const tableClassNames = ['responsive', tableClassName].filter((value): value is string => Boolean(value)).join(' '); return (
- - - +
+ + : } sortColumn={sortColumn} @@ -85,7 +88,7 @@ function MarketsTable({ targetColumn={SortColumn.Starred} showDirection={false} /> - + Id - + Oracle )} - - - - - + + + -
Id Oracle {' '} Risk{' '} - + {' '} Indicators{' '} - + {' '} Actions{' '} -
+
- +
- - - - - - - - - + + + Market + {rateLabel} + Util + Supplied Amount + + + {paginatedPositions.map((position) => { const userConfirmedSupply = BigInt(position.state.supplyAssets); const pendingDeltaBigInt = BigInt(position.pendingDelta); @@ -77,14 +78,14 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM const isSelected = position.market.uniqueKey === selectedMarketUniqueKey; const apyPreview = getApyPreview(position); return ( - onSelectMarket(position.market.uniqueKey)} className={`cursor-pointer border-b border-l-2 border-l-transparent border-gray-200 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800 ${ isSelected ? 'bg-primary/5 border-l-primary' : '' }`} > - - - - - + + ); })} - -
Market{rateLabel}UtilSupplied Amount
+
-
+ + {apyPreview ? ( @@ -121,8 +122,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM % )} - + + {apyPreview ? ( {formatReadable(position.market.state.utilization * 100)}% @@ -134,8 +135,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM {formatReadable(position.market.state.utilization * 100)}% )} - + +
{formatReadable( @@ -160,24 +161,22 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM Max
-
+ +
{totalPages > 1 && ( -
- -
+ )} )} diff --git a/app/positions/components/PositionsSummaryTable.tsx b/app/positions/components/PositionsSummaryTable.tsx index ee04e668..df3474fe 100644 --- a/app/positions/components/PositionsSummaryTable.tsx +++ b/app/positions/components/PositionsSummaryTable.tsx @@ -10,6 +10,7 @@ import { IoChevronDownOutline } from 'react-icons/io5'; import { PiHandCoins } from 'react-icons/pi'; import { PulseLoader } from 'react-spinners'; import { useConnection } from 'wagmi'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { TokenIcon } from '@/components/TokenIcon'; import { TooltipContent } from '@/components/TooltipContent'; @@ -281,15 +282,15 @@ export function PositionsSummaryTable({
-
- - - - - - -
- NetworkSize{rateLabel} (now) +
+ + + + + Network + Size + {rateLabel} (now) + Interest Accrued ({earningsPeriod}) - - - - - - - + + Collateral + Warnings + Actions + + + {processedPositions.map((groupedPosition) => { const rowKey = `${groupedPosition.loanAssetAddress}-${groupedPosition.chainId}`; const isExpanded = expandedRows.has(rowKey); @@ -326,12 +327,12 @@ export function PositionsSummaryTable({ return ( - toggleRow(rowKey)} > - - - - - - - - - + + {expandedRows.has(rowKey) && ( - - - + + )} ); })} - -
CollateralWarningsActions
{isExpanded ? : } + {isExpanded ? : } +
-
+ +
{formatReadable(groupedPosition.totalSupply)} {groupedPosition.loanAsset} @@ -353,13 +354,13 @@ export function PositionsSummaryTable({ height={16} />
-
+ +
{formatReadable((isAprDisplay ? convertApyToApr(avgApy) : avgApy) * 100)}%
-
+ +
{isLoadingEarnings ? (
@@ -382,8 +383,8 @@ export function PositionsSummaryTable({ )}
-
+ +
{groupedPosition.collaterals.length > 0 ? ( groupedPosition.collaterals @@ -403,16 +404,16 @@ export function PositionsSummaryTable({ No known collaterals )}
-
+
-
+ @@ -434,12 +435,12 @@ export function PositionsSummaryTable({ Rebalance -
+ @@ -459,15 +460,15 @@ export function PositionsSummaryTable({ showCollateralExposure={showCollateralExposure} /> -
+ +
{showRebalanceModal && selectedGroupedPosition && ( 0 ? (suppliedAmount / totalSupply) * 100 : 0; return ( - - @@ -51,8 +52,8 @@ function MarketRow({ showNetworkIcon={false} /> - - + @@ -63,20 +64,20 @@ function MarketRow({ chainId={position.market.morphoBlue.chain.id} wide /> - - + - - + {formatReadable(suppliedAmount)} {position.market.loanAsset.symbol} - - + @@ -89,8 +90,8 @@ function MarketRow({ {formatReadable(percentageOfPortfolio)}% - - + @@ -98,8 +99,8 @@ function MarketRow({ market={position.market} showRisk /> - - + @@ -125,8 +126,8 @@ function MarketRow({ Supply - - + + ); } @@ -203,19 +204,19 @@ export function SuppliedMarketsDetail({ )} {/* Markets Table - Always visible */} - - - - - - - - - - - - - +
Market Collateral & Parameters {rateLabel}Supplied% of PortfolioIndicatorsActions
+ + + Market + Collateral & Parameters + {rateLabel} + Supplied + % of Portfolio + Indicators + Actions + + + {filteredMarkets.map((position) => ( ))} - -
+ + ); diff --git a/app/positions/components/onboarding/SetupPositions.tsx b/app/positions/components/onboarding/SetupPositions.tsx index 1dc8f7c1..41e8a45a 100644 --- a/app/positions/components/onboarding/SetupPositions.tsx +++ b/app/positions/components/onboarding/SetupPositions.tsx @@ -4,6 +4,7 @@ import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons'; import Image from 'next/image'; import { formatUnits, parseUnits } from 'viem'; import { Button } from '@/components/ui/button'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { ExecuteTransactionButton } from '@/components/ui/ExecuteTransactionButton'; import Input from '@/components/Input/Input'; import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '@/components/MarketIdentity'; @@ -252,26 +253,26 @@ export function SetupPositions() { {/* Markets Distribution */}
- - - - - - - - - +
Market{rateLabel}Distribution
+ + + Market + {rateLabel} + Distribution + + + {selectedMarkets.map((market) => { const currentPercentage = percentages[market.uniqueKey] ?? 0; const isLocked = lockedAmounts.has(market.uniqueKey); return ( - {/* Market Identity */} - + {/* APY/APR */} - + {/* Distribution Controls */} - - + + ); })} - -
- + - @@ -335,12 +336,12 @@ export function SetupPositions() { -
+ +
{error &&
{error}
} diff --git a/app/positions/report/components/ReportTable.tsx b/app/positions/report/components/ReportTable.tsx index b3d89a8b..12e2921f 100644 --- a/app/positions/report/components/ReportTable.tsx +++ b/app/positions/report/components/ReportTable.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import type { DateValue } from '@heroui/react'; -import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import { getLocalTimeZone } from '@internationalized/date'; import { ExternalLinkIcon } from '@radix-ui/react-icons'; import { useDateFormatter } from '@react-aria/i18n'; @@ -262,19 +262,15 @@ export function ReportTable({ report, asset, startDate, endDate, chainId }: Repo {/* Transactions Table */} {marketReport.transactions.length > 0 && ( -
- +
+
- TYPE - DATE - AMOUNT - TX + + TYPE + DATE + AMOUNT + TX + {marketReport.transactions.map((tx) => ( diff --git a/app/rewards/components/RewardTable.tsx b/app/rewards/components/RewardTable.tsx index 8fa866e4..a05c8f57 100644 --- a/app/rewards/components/RewardTable.tsx +++ b/app/rewards/components/RewardTable.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from 'react'; import { ExternalLinkIcon } from '@radix-ui/react-icons'; -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@heroui/react'; +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; import Image from 'next/image'; import Link from 'next/link'; import type { Address } from 'viem'; @@ -15,8 +15,8 @@ import { useClaimMerklRewards } from '@/hooks/useClaimMerklRewards'; import { useStyledToast } from '@/hooks/useStyledToast'; import { useTransactionWithToast } from '@/hooks/useTransactionWithToast'; import { formatBalance, formatSimple } from '@/utils/balance'; -import { getAssetURL, getMerklCampaignURL } from '@/utils/external'; -import { getNetworkImg } from '@/utils/networks'; +import { getMerklCampaignURL } from '@/utils/external'; +import { getNetworkImg, getNetworkName } from '@/utils/networks'; import { findToken } from '@/utils/tokens'; import type { AggregatedRewardType } from '@/utils/types'; @@ -137,147 +137,147 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr return (
-
- - ASSET - CHAIN - CLAIMABLE - CAMPAIGN - ACTIONS - - - {filteredRewardTokens - .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined) - .map((tokenReward, index) => { - // try find the reward token, default to 18 decimals for unknown tokens - const matchedToken = findToken(tokenReward.asset.address, tokenReward.asset.chain_id) ?? { - symbol: 'Unknown', - img: undefined, - decimals: 18, - }; +
+
+ + + ASSET + CHAIN + CLAIMABLE + CAMPAIGN + ACTIONS + + + + {filteredRewardTokens + .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined) + .map((tokenReward, index) => { + // try find the reward token, default to 18 decimals for unknown tokens + const matchedToken = findToken(tokenReward.asset.address, tokenReward.asset.chain_id) ?? { + symbol: 'Unknown', + img: undefined, + decimals: 18, + }; - const distribution = distributions.find( - (d) => - d.asset.address.toLowerCase() === tokenReward.asset.address.toLowerCase() && - d.asset.chain_id === tokenReward.asset.chain_id, - ); + const distribution = distributions.find( + (d) => + d.asset.address.toLowerCase() === tokenReward.asset.address.toLowerCase() && + d.asset.chain_id === tokenReward.asset.chain_id, + ); - const isMerklReward = tokenReward.programs.includes('merkl'); + const isMerklReward = tokenReward.programs.includes('merkl'); - // Find matching campaign for this reward - const matchedCampaign = campaigns.find( - (c) => - c.rewardToken.address.toLowerCase() === tokenReward.asset.address.toLowerCase() && - c.chainId === tokenReward.asset.chain_id, - ); + // Find matching campaign for this reward + const matchedCampaign = campaigns.find( + (c) => + c.rewardToken.address.toLowerCase() === tokenReward.asset.address.toLowerCase() && + c.chainId === tokenReward.asset.chain_id, + ); - // Create unique key for tracking claim status - const rewardKey = `${tokenReward.asset.address.toLowerCase()}-${tokenReward.asset.chain_id}`; - const isThisRewardClaiming = claimingRewardKey === rewardKey; + // Create unique key for tracking claim status + const rewardKey = `${tokenReward.asset.address.toLowerCase()}-${tokenReward.asset.chain_id}`; + const isThisRewardClaiming = claimingRewardKey === rewardKey; - return ( - - - e.stopPropagation()} - > - {matchedToken.symbol} - - - - - {getNetworkImg(tokenReward.asset.chain_id) ? ( - - ) : ( -
- )} - - -
- {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))} - -
-
- - {matchedCampaign ? ( - + +
+ + {matchedToken.symbol} +
+
+ +
+
+ {networkImg && ( + + )} + {networkName} +
+
+
+ +
+ + {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))} +
+
+ +
+ {matchedCampaign ? ( + + view + + + ) : ( + - + )} +
+
+ +
+ {isMerklReward ? ( + + ) : ( + )} - target="_blank" - rel="noopener noreferrer" - className="inline-flex items-center gap-1 text-sm hover:opacity-80 no-underline" - > - Details - - - ) : ( - - - )} - - - {isMerklReward ? ( - - ) : ( - - )} - - - ); - })} - -
+
+
+ + ); + })} + + + ); } diff --git a/docs/Styling.md b/docs/Styling.md index 7fdd7cae..5572674a 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -465,6 +465,61 @@ Add an action link (like explorer) in the top-right corner: - Render token avatars with `TokenIcon` (`@/components/TokenIcon`) so chain-specific fallbacks, glyph sizing, and tooltips stay consistent. - Display oracle provenance data with `OracleVendorBadge` (`@/components/OracleVendorBadge`) instead of plain text to benefit from vendor icons, warnings, and tooltips. +### Table Component + +**Always use Table components from `@/components/ui/table`** - never use raw HTML `` tags. + +**Components:** +```tsx +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; +import { TablePagination } from '@/components/common/TablePagination'; +``` + +**Basic Usage:** +```tsx +
+ + + Name + Amount + + + + + John + $100 + + +
+ +{/* Pagination - always use TablePagination */} + +``` + +**Key Rules:** + +1. **Variants**: Use `table-body-compact` on `` for activity/transaction tables (adds 6px cushion vs standard padding) +2. **Alignment**: Headers and cells must match - use `text-left`, `text-right`, or `text-center` +3. **Amount Columns**: Use flexbox with `justify-end` for right-aligned token icons, and use `text-sm` for text: + ```tsx + +
+ {amount} + +
+
+ ``` +4. **Pagination**: Always use `TablePagination` component (not HeroUI `Pagination`) + +**Styling:** All styling applied via `app/global.css` - don't add inline styles or override padding. + ### TablePagination Component **TablePagination** (`@/components/common/TablePagination`) diff --git a/package.json b/package.json index 372b5515..24811f9f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@heroui/input": "^2.2.2", "@heroui/react": "^2.4.2", "@heroui/system": "^2.2.2", - "@heroui/table": "^2.0.36", "@heroui/theme": "^2.2.6", "@heroui/tooltip": "^2.0.36", "@internationalized/date": "^3.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e694ab6c..8d36b41e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: '@heroui/system': specifier: ^2.2.2 version: 2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@heroui/table': - specifier: ^2.0.36 - version: 2.2.24(@heroui/system@2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@heroui/theme@2.4.20(tailwindcss@4.1.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@heroui/theme': specifier: ^2.2.6 version: 2.4.20(tailwindcss@4.1.17) @@ -6506,7 +6503,7 @@ snapshots: '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -9150,7 +9147,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.1 + debug: 4.4.3 lodash.memoize: 4.1.2 pony-cause: 2.1.11 semver: 7.7.2 @@ -9166,7 +9163,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.1 + debug: 4.4.3 pony-cause: 2.1.11 semver: 7.7.2 uuid: 9.0.1 @@ -9181,7 +9178,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.1 + debug: 4.4.3 pony-cause: 2.1.11 semver: 7.7.2 uuid: 9.0.1 diff --git a/src/components/common/MarketInfoBlock.tsx b/src/components/common/MarketInfoBlock.tsx index 0d76d538..481fc5c0 100644 --- a/src/components/common/MarketInfoBlock.tsx +++ b/src/components/common/MarketInfoBlock.tsx @@ -71,7 +71,7 @@ export function MarketInfoBlockCompact({ market, amount, className }: MarketInfo />
- {market.collateralAsset.symbol} + {market.collateralAsset.symbol} {formatUnits(BigInt(market.lltv), 16)}% LTV diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx index 11094620..43c51956 100644 --- a/src/components/common/MarketsTableWithSameLoanAsset.tsx +++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx @@ -1033,7 +1033,7 @@ export function MarketsTableWithSameLoanAsset({ {/* Table */}
- + {showSelectColumn && (
+ * {items.map((item) => ( + * + * toggleExpanded(item.id)}> + * ... main row content ... + * + * + * ... expanded content ... + * + * + * ))} + * + * ``` + */ +export function ExpandableRow({ isExpanded, colSpan, children, className }: ExpandableRowProps) { + return ( + + {isExpanded && ( + + + +
{children}
+
+
+
+ )} +
+ ); +} diff --git a/src/components/ui/data-table/SortableHead.tsx b/src/components/ui/data-table/SortableHead.tsx new file mode 100644 index 00000000..fac22395 --- /dev/null +++ b/src/components/ui/data-table/SortableHead.tsx @@ -0,0 +1,29 @@ +import type * as React from 'react'; +import { ArrowDownIcon, ArrowUpIcon } from '@radix-ui/react-icons'; +import { TableHead } from '../table'; + +type SortableHeadProps = { + label: string | React.ReactNode; + sortKey: string; + currentSortKey: string; + direction: 'asc' | 'desc'; + onSort: (key: string) => void; + showDirection?: boolean; + className?: string; +}; + +export function SortableHead({ label, sortKey, currentSortKey, direction, onSort, showDirection = true, className }: SortableHeadProps) { + const isSorting = currentSortKey === sortKey; + + return ( + onSort(sortKey)} + > +
+
{label}
+ {showDirection && isSorting && (direction === 'desc' ? : )} +
+
+ ); +} diff --git a/src/components/ui/data-table/index.ts b/src/components/ui/data-table/index.ts new file mode 100644 index 00000000..66ca92ff --- /dev/null +++ b/src/components/ui/data-table/index.ts @@ -0,0 +1,2 @@ +export { SortableHead } from './SortableHead'; +export { ExpandableRow } from './ExpandableRow'; diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx deleted file mode 100644 index e42a7759..00000000 --- a/src/components/ui/pagination.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { forwardRef } from 'react'; -import { ChevronLeftIcon, ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'; -import { type ButtonProps, buttonVariants } from '@/components/ui/button'; -import { cn } from '@/lib/utils'; - -function Pagination({ className, ...props }: React.ComponentProps<'nav'>) { - return ( -