From be8c1cdd2a8048821d1d315d35ee85d934f65d9e Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 21:16:26 +0800 Subject: [PATCH 1/7] refactor: add shadcn table --- app/global.css | 38 +---- app/history/components/HistoryTable.tsx | 82 ++++++----- .../[marketid]/components/BorrowersTable.tsx | 55 ++++---- .../[marketid]/components/BorrowsTable.tsx | 58 ++++---- .../components/LiquidationsTable.tsx | 61 ++++---- .../[marketid]/components/SuppliersTable.tsx | 51 +++---- .../[marketid]/components/SuppliesTable.tsx | 58 ++++---- app/rewards/components/RewardTable.tsx | 44 +++--- .../ui/data-table/ExpandableRow.tsx | 58 ++++++++ src/components/ui/data-table/SortableHead.tsx | 37 +++++ src/components/ui/data-table/index.ts | 2 + src/components/ui/table.tsx | 130 ++++++++++++++++++ src/hooks/useTableSort.ts | 63 +++++++++ 13 files changed, 503 insertions(+), 234 deletions(-) create mode 100644 src/components/ui/data-table/ExpandableRow.tsx create mode 100644 src/components/ui/data-table/SortableHead.tsx create mode 100644 src/components/ui/data-table/index.ts create mode 100644 src/components/ui/table.tsx create mode 100644 src/hooks/useTableSort.ts diff --git a/app/global.css b/app/global.css index 44053dee..27179b35 100644 --- a/app/global.css +++ b/app/global.css @@ -303,43 +303,7 @@ h1 { padding-bottom: 0.5rem; } -.table-header { - background-color: var(--color-background-secondary); - font-size: 0.8em; - color: grey; -} - -.table-header th { - padding-top: 1rem; - padding-bottom: 1rem; - text-align: center; -} - -.table-body { - background-color: var(--color-background-secondary); - border-left: 2px solid var(--color-background-secondary); -} - -.table-body td { - padding: 1rem; - padding-left: 1rem; - padding-right: 1rem; - text-align: center; -} - -.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr) { - border-left: 2px solid transparent; -} - -.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr):hover { - background-color: var(--palette-bg-hovered); - border-left: 2px solid var(--palette-orange); -} - -.table-body-focused { - background-color: var(--palette-bg-hovered); - border-left: 2px solid var(--palette-orange) !important; -} +/* Table styles have been moved to src/components/ui/table.tsx */ svg { display: block; diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index 666b6ed6..4890eee2 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -1,6 +1,7 @@ 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, Pagination } 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'; @@ -252,38 +253,29 @@ 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) => { + <> +
+
+ + + 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; @@ -388,10 +380,26 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab - ); - })} - -
+ ); + }) + )} + + + + {totalPages > 1 && ( +
+ +
+ )} + )} ); diff --git a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx index f536d884..de6ee0ea 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,27 +109,29 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen )} - - - ACCOUNT - BORROWED - COLLATERAL - LTV - % OF BORROW - - +
- {borrowersWithLTV.map((borrower) => { + + + ACCOUNT + BORROWED + COLLATERAL + LTV + % OF BORROW + + + + {borrowersWithLTV.length === 0 && !isLoading ? ( + + + No borrowers found for this market + + + ) : ( + borrowersWithLTV.map((borrower) => { const totalBorrow = BigInt(market.state.borrowAssets); const borrowerAssets = BigInt(borrower.borrowAssets); const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0; @@ -174,10 +177,12 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen {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..c679b704 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,32 +93,29 @@ export function BorrowsTable({ chainId, market, minAssets, onOpenFiltersModal }: )} - - - ACCOUNT - TYPE - AMOUNT - TIME - - TRANSACTION - - - +
- {borrows.map((borrow) => ( + + + ACCOUNT + TYPE + AMOUNT + TIME + TRANSACTION + + + + {borrows.length === 0 && !isLoading ? ( + + + No borrow activities found for this market + + + ) : ( + borrows.map((borrow) => ( - ))} - -
+ )) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 9b9cf4b1..25e6c3b1 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'; @@ -51,33 +51,30 @@ 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) => { + + + 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 + + + ) : ( + paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => { const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0); const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x'); @@ -150,10 +147,12 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) { /> - ); - })} - -
+ ); + }) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx index d73af9a1..bea0d467 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,25 +102,27 @@ export function SuppliersTable({ chainId, market, minShares, onOpenFiltersModal )} - - - ACCOUNT - SUPPLIED - % OF SUPPLY - - +
- {suppliersWithAssets.map((supplier) => { + + + ACCOUNT + SUPPLIED + % OF SUPPLY + + + + {suppliersWithAssets.length === 0 && !isLoading ? ( + + + No suppliers found for this market + + + ) : ( + suppliersWithAssets.map((supplier) => { const totalSupply = BigInt(market.state.supplyAssets); const supplierAssets = BigInt(supplier.supplyAssets); const percentOfSupply = totalSupply > 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0; @@ -150,10 +153,12 @@ export function SuppliersTable({ chainId, market, minShares, onOpenFiltersModal {percentDisplay} - ); - })} - -
+ ); + }) + )} + + + {totalCount > 0 && ( diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx index 12a2acb1..ca222461 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,32 +88,29 @@ export function SuppliesTable({ chainId, market, minAssets, onOpenFiltersModal } )} - - - ACCOUNT - TYPE - AMOUNT - TIME - - TRANSACTION - - - +
- {supplies.map((supply) => ( + + + ACCOUNT + TYPE + AMOUNT + TIME + TRANSACTION + + + + {supplies.length === 0 && !isLoading ? ( + + + No supply activities found for this market + + + ) : ( + supplies.map((supply) => ( - ))} - -
+ )) + )} + + + {totalCount > 0 && ( diff --git a/app/rewards/components/RewardTable.tsx b/app/rewards/components/RewardTable.tsx index 8fa866e4..72612340 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'; @@ -137,24 +137,21 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr return (
- - - ASSET - CHAIN - CLAIMABLE - CAMPAIGN - ACTIONS - - - {filteredRewardTokens - .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined) - .map((tokenReward, index) => { +
+
+ + + 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', @@ -274,10 +271,11 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr )} - ); - })} - -
+ ); + })} + + +
); } diff --git a/src/components/ui/data-table/ExpandableRow.tsx b/src/components/ui/data-table/ExpandableRow.tsx new file mode 100644 index 00000000..45ae67af --- /dev/null +++ b/src/components/ui/data-table/ExpandableRow.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { TableRow, TableCell } from '../table'; + +type ExpandableRowProps = { + isExpanded: boolean; + colSpan: number; + children: React.ReactNode; + className?: string; +}; + +/** + * ExpandableRow component for animated row expansion in tables. + * + * Usage: + * ```tsx + * + * {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..917e44c6 --- /dev/null +++ b/src/components/ui/data-table/SortableHead.tsx @@ -0,0 +1,37 @@ +import * 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/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000..7f3d66b1 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,130 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/hooks/useTableSort.ts b/src/hooks/useTableSort.ts new file mode 100644 index 00000000..97a82f1a --- /dev/null +++ b/src/hooks/useTableSort.ts @@ -0,0 +1,63 @@ +import { useState, useMemo } from 'react'; + +type SortDirection = 'asc' | 'desc'; + +type SortFn = (a: T, b: T) => number; + +type SortConfig = { + key: string; + fn: SortFn; +}; + +type UseTableSortProps = { + data: T[]; + sortFns: Record>; + initialSortKey?: string; + initialDirection?: SortDirection; +}; + +type UseTableSortReturn = { + sortKey: string; + sortDirection: SortDirection; + handleSort: (key: string) => void; + sortedData: T[]; +}; + +export function useTableSort({ + data, + sortFns, + initialSortKey = '', + initialDirection = 'desc', +}: UseTableSortProps): UseTableSortReturn { + const [sortKey, setSortKey] = useState(initialSortKey); + const [sortDirection, setSortDirection] = useState(initialDirection); + + const handleSort = (key: string) => { + if (sortKey === key) { + // Toggle direction if clicking the same column + setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc')); + } else { + // New column, default to descending + setSortKey(key); + setSortDirection('desc'); + } + }; + + const sortedData = useMemo(() => { + if (!sortKey || !sortFns[sortKey]) { + return data; + } + + const sortFn = sortFns[sortKey]; + const sorted = [...data].sort(sortFn); + + return sortDirection === 'asc' ? sorted : sorted.reverse(); + }, [data, sortKey, sortDirection, sortFns]); + + return { + sortKey, + sortDirection, + handleSort, + sortedData, + }; +} From 8b1d3ea67bfaa025b1d434f3fbf2646206cf9c5e Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 21:21:42 +0800 Subject: [PATCH 2/7] misc: cleanup --- app/history/components/HistoryTable.tsx | 204 ++++++++-------- .../[marketid]/components/BorrowersTable.tsx | 93 ++++---- .../[marketid]/components/BorrowsTable.tsx | 73 +++--- .../components/LiquidationsTable.tsx | 131 ++++++----- .../[marketid]/components/SuppliersTable.tsx | 63 ++--- .../[marketid]/components/SuppliesTable.tsx | 73 +++--- .../report/components/ReportTable.tsx | 22 +- app/rewards/components/RewardTable.tsx | 218 +++++++++--------- package.json | 1 - pnpm-lock.yaml | 11 +- .../ui/data-table/ExpandableRow.tsx | 2 +- src/components/ui/data-table/SortableHead.tsx | 12 +- src/components/ui/table.tsx | 120 +++------- src/hooks/useTableSort.ts | 5 - 14 files changed, 491 insertions(+), 537 deletions(-) diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index 4890eee2..d4782b0c 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -268,7 +268,10 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab {history.filter((tx) => tx.data.market !== undefined).length === 0 ? ( - + No transactions found @@ -276,112 +279,113 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab 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; + // 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 && ( - network - )} - {networkName} -
-
-
+ return ( + + {/* Network & Asset */} + +
+
+ + {market.loanAsset.symbol} +
+
+ {networkImg && ( + network + )} + {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 */} + +
+ +
+
+
+ ); + }) )}
diff --git a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx index de6ee0ea..4438bb99 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx @@ -126,57 +126,60 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen {borrowersWithLTV.length === 0 && !isLoading ? ( - + No borrowers found for this market ) : ( 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 && ( - - 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0; + const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`; + + return ( + + + - - )} - - {borrower.ltv.toFixed(2)}% - {percentDisplay} - + + + {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} + ); }) )} diff --git a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx index c679b704..feaa7a0b 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx @@ -110,47 +110,50 @@ export function BorrowsTable({ chainId, market, minAssets, onOpenFiltersModal }: {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()} - - - - + + + + {borrow.type === 'MarketBorrow' ? 'Borrow' : 'Repay'} + + + + {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + + + )} + + {moment.unix(borrow.timestamp).fromNow()} + + + + )) )} diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 25e6c3b1..a36aad45 100644 --- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx @@ -69,60 +69,33 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) { {paginatedLiquidations.length === 0 && !isLoading ? ( - + No liquidations found for this market ) : ( paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => { - const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0); - const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x'); + 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 && ( - - - - )} - - - {hasBadDebt ? ( - <> - {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} + return ( + + + {isLiquidatorAddress ? ( + + ) : ( + {liquidation.liquidator} + )} + + + {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)} {market?.loanAsset?.symbol && ( )} - - ) : ( - ' - ' - )} - - {moment.unix(liquidation.timestamp).fromNow()} - - - - - ); - }) - )} + + + {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)} + {market?.collateralAsset?.symbol && ( + + + + )} + + + {hasBadDebt ? ( + <> + {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} + {market?.loanAsset?.symbol && ( + + + + )} + + ) : ( + ' - ' + )} + + {moment.unix(liquidation.timestamp).fromNow()} + + + + + ); + }) + )}
diff --git a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx index bea0d467..06b98500 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx @@ -117,42 +117,45 @@ export function SuppliersTable({ chainId, market, minShares, onOpenFiltersModal {suppliersWithAssets.length === 0 && !isLoading ? ( - + No suppliers found for this market ) : ( 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 && ( - - 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0; + const percentDisplay = percentOfSupply < 0.01 && percentOfSupply > 0 ? '<0.01%' : `${percentOfSupply.toFixed(2)}%`; + + return ( + + + - - )} - - {percentDisplay} - + + + {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + + + )} + + {percentDisplay} + ); }) )} diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx index ca222461..0dc7a9da 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx @@ -105,47 +105,50 @@ export function SuppliesTable({ chainId, market, minAssets, onOpenFiltersModal } {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()} - - - - + + + + {supply.type === 'MarketSupply' ? 'Supply' : 'Withdraw'} + + + + {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( + + + + )} + + {moment.unix(supply.timestamp).fromNow()} + + + + )) )} 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 72612340..46af8653 100644 --- a/app/rewards/components/RewardTable.tsx +++ b/app/rewards/components/RewardTable.tsx @@ -152,125 +152,125 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr {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, - }; + // 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 ? ( + return ( + + e.stopPropagation()} > - Details - + {matchedToken.symbol} + - ) : ( - - - )} - - - {isMerklReward ? ( - - ) : ( - - )} - - + + + {getNetworkImg(tokenReward.asset.chain_id) ? ( + + ) : ( +
+ )} + + +
+ {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))} + +
+
+ + {matchedCampaign ? ( + + Details + + + ) : ( + - + )} + + + {isMerklReward ? ( + + ) : ( + + )} + + ); })} 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/ui/data-table/ExpandableRow.tsx b/src/components/ui/data-table/ExpandableRow.tsx index 45ae67af..1a937eb8 100644 --- a/src/components/ui/data-table/ExpandableRow.tsx +++ b/src/components/ui/data-table/ExpandableRow.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import type * as React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { TableRow, TableCell } from '../table'; diff --git a/src/components/ui/data-table/SortableHead.tsx b/src/components/ui/data-table/SortableHead.tsx index 917e44c6..fac22395 100644 --- a/src/components/ui/data-table/SortableHead.tsx +++ b/src/components/ui/data-table/SortableHead.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import type * as React from 'react'; import { ArrowDownIcon, ArrowUpIcon } from '@radix-ui/react-icons'; import { TableHead } from '../table'; @@ -12,15 +12,7 @@ type SortableHeadProps = { className?: string; }; -export function SortableHead({ - label, - sortKey, - currentSortKey, - direction, - onSort, - showDirection = true, - className, -}: SortableHeadProps) { +export function SortableHead({ label, sortKey, currentSortKey, direction, onSort, showDirection = true, className }: SortableHeadProps) { const isSorting = currentSortKey === sortKey; return ( diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 7f3d66b1..3462af42 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -1,130 +1,86 @@ -import * as React from "react" +import { forwardRef } from 'react'; +import type { HTMLAttributes, ThHTMLAttributes, TdHTMLAttributes } from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -const Table = React.forwardRef< - HTMLTableElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const Table = forwardRef>(({ className, ...props }, ref) => (
-)) -Table.displayName = "Table" +)); +Table.displayName = 'Table'; -const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const TableHeader = forwardRef>(({ className, ...props }, ref) => ( -)) -TableHeader.displayName = "TableHeader" +)); +TableHeader.displayName = 'TableHeader'; -const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const TableBody = forwardRef>(({ className, ...props }, ref) => ( -)) -TableBody.displayName = "TableBody" +)); +TableBody.displayName = 'TableBody'; -const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const TableFooter = forwardRef>(({ className, ...props }, ref) => ( -)) -TableFooter.displayName = "TableFooter" +)); +TableFooter.displayName = 'TableFooter'; -const TableRow = React.forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const TableRow = forwardRef>(({ className, ...props }, ref) => ( -)) -TableRow.displayName = "TableRow" +)); +TableRow.displayName = 'TableRow'; -const TableHead = React.forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes ->(({ className, ...props }, ref) => ( +const TableHead = forwardRef>(({ className, ...props }, ref) => ( + ); } @@ -92,10 +93,10 @@ export function AssetMetricsTable({ data }: AssetMetricsTableProps) { {processedData.length === 0 ? (
No asset data available
) : ( -
[role=checkbox]]:translate-y-[2px]", - className + 'py-4 px-2 text-center align-middle font-normal [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', + className, )} {...props} /> -)) -TableHead.displayName = "TableHead" +)); +TableHead.displayName = 'TableHead'; -const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( +const TableCell = forwardRef>(({ className, ...props }, ref) => ( [role=checkbox]]:translate-y-[2px]", - className - )} + className={cn('p-4 text-center align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className)} {...props} /> -)) -TableCell.displayName = "TableCell" +)); +TableCell.displayName = 'TableCell'; -const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( +const TableCaption = forwardRef>(({ className, ...props }, ref) => (
-)) -TableCaption.displayName = "TableCaption" +)); +TableCaption.displayName = 'TableCaption'; -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/src/hooks/useTableSort.ts b/src/hooks/useTableSort.ts index 97a82f1a..ae49e0af 100644 --- a/src/hooks/useTableSort.ts +++ b/src/hooks/useTableSort.ts @@ -4,11 +4,6 @@ type SortDirection = 'asc' | 'desc'; type SortFn = (a: T, b: T) => number; -type SortConfig = { - key: string; - fn: SortFn; -}; - type UseTableSortProps = { data: T[]; sortFns: Record>; From 393e8a1db09d0385117814cbfb2f7d71ff34c96b Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 22:52:05 +0800 Subject: [PATCH 3/7] refactor: table usages --- .../stats/components/AssetMetricsTable.tsx | 51 ++-- .../stats/components/TransactionTableBody.tsx | 37 +-- .../stats/components/TransactionsTable.tsx | 27 +- .../components/allocations/CollateralView.tsx | 45 ++-- .../components/allocations/MarketView.tsx | 57 ++-- app/autovault/components/VaultListV2.tsx | 50 ++-- app/global.css | 45 +++- app/history/components/HistoryTable.tsx | 244 +++++++++--------- .../[marketid]/components/BorrowersTable.tsx | 30 +-- .../[marketid]/components/BorrowsTable.tsx | 20 +- .../components/LiquidationsTable.tsx | 58 ++--- .../[marketid]/components/SuppliersTable.tsx | 16 +- .../[marketid]/components/SuppliesTable.tsx | 20 +- app/markets/components/MarketTableBody.tsx | 65 ++--- app/markets/components/MarketTableUtils.tsx | 13 +- app/markets/components/marketsTable.tsx | 33 +-- app/positions/components/FromMarketsTable.tsx | 44 ++-- .../components/PositionsSummaryTable.tsx | 79 +++--- .../components/SuppliedMarketsDetail.tsx | 63 ++--- .../components/onboarding/SetupPositions.tsx | 39 +-- app/rewards/components/RewardTable.tsx | 145 ++++++----- docs/Styling.md | 227 ++++++++++++++++ .../common/MarketsTableWithSameLoanAsset.tsx | 2 +- src/components/ui/table.tsx | 28 +- 24 files changed, 857 insertions(+), 581 deletions(-) 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' ? : )} -
- - - +
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..a7a7fdc4 100644 --- a/app/autovault/components/VaultListV2.tsx +++ b/app/autovault/components/VaultListV2.tsx @@ -51,17 +51,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 +71,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 +101,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 27179b35..c9f6f073 100644 --- a/app/global.css +++ b/app/global.css @@ -303,7 +303,50 @@ h1 { padding-bottom: 0.5rem; } -/* Table styles have been moved to src/components/ui/table.tsx */ +/* Table styles - applied automatically to thead and tbody */ +thead { + background-color: var(--color-background-secondary); + font-size: 0.8em; + color: grey; +} + +thead th { + padding: 1rem; + padding-left: 1rem; + padding-right: 1rem; +} + +tbody { + background-color: var(--color-background-secondary); + border-left: 2px solid var(--color-background-secondary); +} + +tbody td { + padding: 1rem; + padding-left: 1rem; + padding-right: 1rem; +} + +tbody tr:not(.no-hover-effect tr, .no-hover-effect tr) { + border-left: 2px solid transparent; +} + +tbody tr:not(.no-hover-effect tr, .no-hover-effect tr):hover { + background-color: var(--palette-bg-hovered); + border-left: 2px solid var(--palette-orange); +} + +.table-body-focused { + background-color: var(--palette-bg-hovered); + 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; diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index d4782b0c..c65e00ab 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -254,142 +254,140 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab ) : ( <> -
- - +
+ + + Asset & Network + Market Details + Action & Amount + Time + Transaction + + + + {history.filter((tx) => tx.data.market !== undefined).length === 0 ? ( - Asset & Network - Market Details - Action & Amount - Time - Transaction + + No transactions found + - - - {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; + ) : ( + 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}
- - - {/* Market Details */} - -
- - {market.uniqueKey.slice(2, 8)} - -
- + {networkImg && ( + - {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 && ( - - - )} + {networkName}
-
- - {/* Time */} - -
{moment.unix(tx.timestamp).fromNow()}
-
+
+
- {/* Transaction */} - -
- +
+ + {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 && ( + + + + )} +
+
+ + {/* Time */} + +
{moment.unix(tx.timestamp).fromNow()}
+
+ + {/* Transaction */} + +
+ +
+
+ + ); + }) + )} +
+ {totalPages > 1 && (
% OF BORROW - + {borrowersWithLTV.length === 0 && !isLoading ? ( - - {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - + +
+ {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( - - )} + )} +
- - {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))} - {market?.collateralAsset?.symbol && ( - + +
+ {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))} + {market?.collateralAsset?.symbol && ( - - )} + )} +
- {borrower.ltv.toFixed(2)}% - {percentDisplay} + {borrower.ltv.toFixed(2)}% + {percentDisplay}
); }) diff --git a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx index feaa7a0b..6ea60051 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx @@ -104,10 +104,10 @@ export function BorrowsTable({ chainId, market, minAssets, onOpenFiltersModal }: TYPE AMOUNT TIME - TRANSACTION + TRANSACTION - + {borrows.length === 0 && !isLoading ? ( - - {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - + +
+ {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( - - )} + )} +
- {moment.unix(borrow.timestamp).fromNow()} - + {moment.unix(borrow.timestamp).fromNow()} + SEIZED ({market?.collateralAsset?.symbol ?? 'Collateral'}) BAD DEBT ({market?.loanAsset?.symbol ?? 'Loan'}) TIME - TRANSACTION + TRANSACTION
- + {paginatedLiquidations.length === 0 && !isLoading ? ( {liquidation.liquidator} )} - - {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)} - {market?.loanAsset?.symbol && ( - + +
+ {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)} + {market?.loanAsset?.symbol && ( - - )} + )} +
- - {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)} - {market?.collateralAsset?.symbol && ( - + +
+ {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)} + {market?.collateralAsset?.symbol && ( - - )} + )} +
- + {hasBadDebt ? ( - <> - {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} +
+ {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} {market?.loanAsset?.symbol && ( - - - + )} - +
) : ( - ' - ' +
-
)}
- {moment.unix(liquidation.timestamp).fromNow()} - + {moment.unix(liquidation.timestamp).fromNow()} + % OF SUPPLY
- + {suppliersWithAssets.length === 0 && !isLoading ? ( - - {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - + +
+ {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( - - )} + )} +
- {percentDisplay} + {percentDisplay}
); }) diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx index 0dc7a9da..2f8090a7 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx @@ -99,10 +99,10 @@ export function SuppliesTable({ chainId, market, minAssets, onOpenFiltersModal } TYPE AMOUNT TIME - TRANSACTION + TRANSACTION - + {supplies.length === 0 && !isLoading ? ( - - {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))} - {market?.loanAsset?.symbol && ( - + +
+ {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))} + {market?.loanAsset?.symbol && ( - - )} + )} +
- {moment.unix(supply.timestamp).fromNow()} - + {moment.unix(supply.timestamp).fromNow()} + + {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 ' : ''}'`} > -

{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..5fdfb8be 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,15 @@ 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 +86,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 +77,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 +121,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM % )} - + + {apyPreview ? ( {formatReadable(position.market.state.utilization * 100)}% @@ -134,8 +134,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM {formatReadable(position.market.state.utilization * 100)}% )} - + +
{formatReadable( @@ -160,12 +160,12 @@ 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/rewards/components/RewardTable.tsx b/app/rewards/components/RewardTable.tsx index 46af8653..3d7145c2 100644 --- a/app/rewards/components/RewardTable.tsx +++ b/app/rewards/components/RewardTable.tsx @@ -141,14 +141,14 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr - ASSET - CHAIN - CLAIMABLE - CAMPAIGN + ASSET + CHAIN + CLAIMABLE + CAMPAIGN ACTIONS - + {filteredRewardTokens .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined) .map((tokenReward, index) => { @@ -185,90 +185,97 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr href={getAssetURL(tokenReward.asset.address, tokenReward.asset.chain_id)} target="_blank" rel="noopener noreferrer" - className="flex items-center gap-2 no-underline hover:opacity-80" + className="flex items-center gap-2 no-underline hover:opacity-80 text-sm" onClick={(e) => e.stopPropagation()} > - {matchedToken.symbol} + {matchedToken.symbol} - {getNetworkImg(tokenReward.asset.chain_id) ? ( - - ) : ( -
- )} +
+ {getNetworkImg(tokenReward.asset.chain_id) ? ( + + ) : ( +
+ )} +
- -
- {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))} + +
+ + {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))}
- {matchedCampaign ? ( - - Details - - - ) : ( - - - )} +
+ {matchedCampaign ? ( + + view + + + ) : ( + - + )} +
- - {isMerklReward ? ( - - ) : ( - - )} + +
+ {isMerklReward ? ( + + ) : ( + + )} +
); diff --git a/docs/Styling.md b/docs/Styling.md index 7fdd7cae..07ab0a1b 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -465,6 +465,233 @@ 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 + +**Table** (`@/components/ui/table`) +- Unified table component system for all tables in the app +- Provides consistent styling, alignment, and spacing +- Built on shadcn/ui primitives with custom Monarch styling via global CSS + +**IMPORTANT**: Always use the Table components instead of raw HTML `
` tags. All styling is automatically applied via element selectors in `app/global.css`, not inline styles or className props. + +**Components:** +- `Table`: The main table wrapper (renders `
`) +- `TableHeader`: Table header section (renders ``) +- `TableBody`: Table body section (renders ``) +- `TableRow`: Table row (renders ``) +- `TableHead`: Header cell (renders `
`) +- `TableCell`: Body cell (renders ``) + +**Basic Structure:** + +```tsx +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; + +
+ + + + Name + Amount + Actions + + + + + John Doe + $100 + + + + + +
+
+``` + +**Styling Rules:** + +1. **Wrapper**: Always wrap tables in `
` +2. **Alignment**: Use Tailwind classes for alignment - they override the global CSS: + - `text-left`: Left-align (default for most content) + - `text-right`: Right-align (use for amounts, numbers, actions) + - `text-center`: Center-align (rarely used) +3. **Headers and cells must match alignment**: If a column is `text-right`, both the header and cells should use `text-right` +4. **Compact variant**: Add `table-body-compact` className to `` for reduced padding + +**Compact Variant:** + +Use the compact variant for simpler tables with less complex data (activity tables, transaction lists): + +```tsx + + + ... + + +``` + +**Differences:** +- Standard: 1rem padding (markets table, positions table) +- Compact: 0.625rem padding (supplies, borrows, liquidations, rewards) + +**Aligning Amount Columns:** + +For columns with amounts and token icons, use flexbox to ensure icons align vertically: + +```tsx +// ✅ Correct - icons align at the right edge + +
+ 1,234.56 + +
+
+ +// ❌ Incorrect - icons won't align + + 1,234.56 + + + + +``` + +**Common Patterns:** + +**1. Address Column (First Column)** + +```tsx +ACCOUNT +... + + + +``` + +**2. Amount Column (Right-Aligned)** + +```tsx +AMOUNT +... + +
+ {formatSimple(amount)} + +
+
+``` + +**3. Percentage/Number Column (Right-Aligned)** + +```tsx +APY +... +12.34% +``` + +**4. Timestamp Column (Smaller Font, Grey)** + +```tsx +TIME +... + + {moment.unix(timestamp).fromNow()} + +``` + +**5. Transaction Hash Column** + +```tsx +TRANSACTION +... + + + +``` + +**Complete Example (Activity Table):** + +```tsx +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; +import { TransactionIdentity } from '@/components/common/TransactionIdentity'; +import { TokenIcon } from '@/components/TokenIcon'; +import moment from 'moment'; + +
+ + + + ACCOUNT + TYPE + AMOUNT + TIME + TRANSACTION + + + + {items.map((item) => ( + + + + + + + {item.type} + + + +
+ {formatSimple(item.amount)} + +
+
+ + {moment.unix(item.timestamp).fromNow()} + + + + +
+ ))} +
+
+
+``` + +**Global CSS Styling:** + +All table styling is automatically applied via element selectors in `app/global.css`: + +- `thead`: Background, font size, and color for all table headers +- `thead th`: Padding for header cells (1rem all around) +- `tbody`: Background and border for all table bodies +- `tbody td`: Padding for body cells (1rem all around) +- `.table-body-compact td`: Reduced padding variant (0.625rem) - apply to `` +- `tbody tr:hover`: Hover effect with orange left border +- `.table-body-focused`: Active/expanded row styling - apply to `` + +**DO NOT**: +- Add inline styles or hardcoded Tailwind padding/alignment to Table components +- Override global CSS in individual components +- Use plain HTML `` tags instead of Table components +- Mix alignment between headers and cells (always match them) +- Apply `className="table-header"` or `className="table-body"` - these are applied automatically via CSS + ### TablePagination Component **TablePagination** (`@/components/common/TablePagination`) 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 && (
>(({ className, ...props }, ref) => ( -
- - +
)); Table.displayName = 'Table'; const TableHeader = forwardRef>(({ className, ...props }, ref) => ( )); @@ -26,7 +24,7 @@ TableHeader.displayName = 'TableHeader'; const TableBody = forwardRef>(({ className, ...props }, ref) => ( )); @@ -44,10 +42,7 @@ TableFooter.displayName = 'TableFooter'; const TableRow = forwardRef>(({ className, ...props }, ref) => ( )); @@ -56,10 +51,7 @@ TableRow.displayName = 'TableRow'; const TableHead = forwardRef>(({ className, ...props }, ref) => (
[role=checkbox]]:translate-y-[2px]', - className, - )} + className={cn('font-normal', className)} {...props} /> )); @@ -68,7 +60,7 @@ TableHead.displayName = 'TableHead'; const TableCell = forwardRef>(({ className, ...props }, ref) => ( [role=checkbox]]:translate-y-[2px]', className)} + className={className} {...props} /> )); From b048c68a9f617ceb5a2712ccb9752a5bcabf35d6 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 22:57:21 +0800 Subject: [PATCH 4/7] feat: remove unused pagination component --- app/autovault/components/VaultListV2.tsx | 1 + app/history/components/HistoryTable.tsx | 28 +++-- app/markets/components/marketsTable.tsx | 4 +- app/positions/components/FromMarketsTable.tsx | 19 ++-- app/rewards/components/RewardTable.tsx | 1 - src/components/ui/pagination.tsx | 104 ------------------ 6 files changed, 26 insertions(+), 131 deletions(-) delete mode 100644 src/components/ui/pagination.tsx diff --git a/app/autovault/components/VaultListV2.tsx b/app/autovault/components/VaultListV2.tsx index a7a7fdc4..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'; diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index c65e00ab..b9154eaf 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -1,6 +1,6 @@ import type React from 'react'; import { useMemo, useState, useRef, useEffect } from 'react'; -import { Chip, Link, Pagination } 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'; @@ -8,6 +8,7 @@ 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'; @@ -301,8 +302,8 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab address={market.loanAsset.address} chainId={market.morphoBlue.chain.id} symbol={market.loanAsset.symbol} - width={20} - height={20} + width={16} + height={16} /> {market.loanAsset.symbol} @@ -353,7 +354,7 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab {/* Action & Amount */} -
+
{actionTypeToText(tx.type)} {sign} @@ -389,17 +390,14 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab
{totalPages > 1 && ( -
- -
+ tx.data.market !== undefined).length} + pageSize={pageSize} + onPageChange={setCurrentPage} + isLoading={loading} + /> )} )} diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx index 5fdfb8be..33538880 100644 --- a/app/markets/components/marketsTable.tsx +++ b/app/markets/components/marketsTable.tsx @@ -69,7 +69,9 @@ 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 = ['bg-surface shadow-sm rounded overflow-hidden', wrapperClassName].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 ( diff --git a/app/positions/components/FromMarketsTable.tsx b/app/positions/components/FromMarketsTable.tsx index ea71790d..118e2f9c 100644 --- a/app/positions/components/FromMarketsTable.tsx +++ b/app/positions/components/FromMarketsTable.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; -import { Pagination } from '@heroui/react'; import { Button } from '@/components/ui/button'; +import { TablePagination } from '@/components/common/TablePagination'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '@/components/MarketIdentity'; import { useMarkets } from '@/hooks/useMarkets'; import { useRateLabel } from '@/hooks/useRateLabel'; @@ -169,15 +170,13 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM
{totalPages > 1 && ( -
- -
+ )} )} diff --git a/app/rewards/components/RewardTable.tsx b/app/rewards/components/RewardTable.tsx index 3d7145c2..35c63d94 100644 --- a/app/rewards/components/RewardTable.tsx +++ b/app/rewards/components/RewardTable.tsx @@ -216,7 +216,6 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr
- ) { - return ( -
{networkImg && ( @@ -376,7 +376,7 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab {/* Transaction */} -
+
` tags. All styling is automatically applied via element selectors in `app/global.css`, not inline styles or className props. +**Always use Table components from `@/components/ui/table`** - never use raw HTML `` tags. **Components:** -- `Table`: The main table wrapper (renders `
`) -- `TableHeader`: Table header section (renders ``) -- `TableBody`: Table body section (renders ``) -- `TableRow`: Table row (renders ``) -- `TableHead`: Header cell (renders `
`) -- `TableCell`: Body cell (renders ``) - -**Basic Structure:** - -```tsx -import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; - -
- - - - Name - Amount - Actions - - - - - John Doe - $100 - - - - - -
-
-``` - -**Styling Rules:** - -1. **Wrapper**: Always wrap tables in `
` -2. **Alignment**: Use Tailwind classes for alignment - they override the global CSS: - - `text-left`: Left-align (default for most content) - - `text-right`: Right-align (use for amounts, numbers, actions) - - `text-center`: Center-align (rarely used) -3. **Headers and cells must match alignment**: If a column is `text-right`, both the header and cells should use `text-right` -4. **Compact variant**: Add `table-body-compact` className to `` for reduced padding - -**Compact Variant:** - -Use the compact variant for simpler tables with less complex data (activity tables, transaction lists): - -```tsx - - - ... - - -``` - -**Differences:** -- Standard: 1rem padding (markets table, positions table) -- Compact: 0.625rem padding (supplies, borrows, liquidations, rewards) - -**Aligning Amount Columns:** - -For columns with amounts and token icons, use flexbox to ensure icons align vertically: - -```tsx -// ✅ Correct - icons align at the right edge - -
- 1,234.56 - -
-
- -// ❌ Incorrect - icons won't align - - 1,234.56 - - - - -``` - -**Common Patterns:** - -**1. Address Column (First Column)** - -```tsx -ACCOUNT -... - - - -``` - -**2. Amount Column (Right-Aligned)** - -```tsx -AMOUNT -... - -
- {formatSimple(amount)} - -
-
-``` - -**3. Percentage/Number Column (Right-Aligned)** - -```tsx -APY -... -12.34% -``` - -**4. Timestamp Column (Smaller Font, Grey)** - -```tsx -TIME -... - - {moment.unix(timestamp).fromNow()} - -``` - -**5. Transaction Hash Column** - ```tsx -TRANSACTION -... - - - +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; +import { TablePagination } from '@/components/common/TablePagination'; ``` -**Complete Example (Activity Table):** - -```tsx -import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table'; -import { AccountIdentity } from '@/components/common/AccountIdentity'; -import { TransactionIdentity } from '@/components/common/TransactionIdentity'; -import { TokenIcon } from '@/components/TokenIcon'; -import moment from 'moment'; - -
- - - - ACCOUNT - TYPE - AMOUNT - TIME - TRANSACTION - - - - {items.map((item) => ( - - - - - - - {item.type} - - - -
- {formatSimple(item.amount)} - -
-
- - {moment.unix(item.timestamp).fromNow()} - - - - -
- ))} -
-
-
+**Basic Usage:** +```tsx + + + + Name + Amount + + + + + John + $100 + + +
+ +{/* Pagination - always use TablePagination */} + ``` -**Global CSS Styling:** - -All table styling is automatically applied via element selectors in `app/global.css`: +**Key Rules:** -- `thead`: Background, font size, and color for all table headers -- `thead th`: Padding for header cells (1rem all around) -- `tbody`: Background and border for all table bodies -- `tbody td`: Padding for body cells (1rem all around) -- `.table-body-compact td`: Reduced padding variant (0.625rem) - apply to `` -- `tbody tr:hover`: Hover effect with orange left border -- `.table-body-focused`: Active/expanded row styling - apply to `` +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 `font-sm` for text: + ```tsx + +
+ {amount} + +
+
+ ``` +4. **Pagination**: Always use `TablePagination` component (not HeroUI `Pagination`) -**DO NOT**: -- Add inline styles or hardcoded Tailwind padding/alignment to Table components -- Override global CSS in individual components -- Use plain HTML `` tags instead of Table components -- Mix alignment between headers and cells (always match them) -- Apply `className="table-header"` or `className="table-body"` - these are applied automatically via CSS +**Styling:** All styling applied via `app/global.css` - don't add inline styles or override padding. ### TablePagination Component From 4f2d483e7d7a33edd7da8266a0037055d4193b91 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 23:40:51 +0800 Subject: [PATCH 6/7] misc: cleanup --- app/global.css | 4 ++-- app/history/components/HistoryTable.tsx | 2 +- .../[chainId]/[marketid]/components/LiquidationsTable.tsx | 7 +++++-- app/markets/components/MarketTableBody.tsx | 2 +- docs/Styling.md | 4 ++-- src/components/common/MarketInfoBlock.tsx | 2 +- src/components/ui/table.tsx | 8 ++++---- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/global.css b/app/global.css index c9f6f073..a6971053 100644 --- a/app/global.css +++ b/app/global.css @@ -327,11 +327,11 @@ tbody td { padding-right: 1rem; } -tbody tr:not(.no-hover-effect tr, .no-hover-effect tr) { +tbody tr:not(.no-hover-effect tr) { border-left: 2px solid transparent; } -tbody 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); } diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx index 2d8fc2e9..8a3c3b6e 100644 --- a/app/history/components/HistoryTable.tsx +++ b/app/history/components/HistoryTable.tsx @@ -393,7 +393,7 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab tx.data.market !== undefined).length} + totalEntries={totalPages * pageSize} pageSize={pageSize} onPageChange={setCurrentPage} isLoading={loading} diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 2948cae0..28673f96 100644 --- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx @@ -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 = { @@ -96,7 +97,7 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) {
- {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)} + {formatSimple(Number(formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)))} {market?.loanAsset?.symbol && (
- {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)} + + {formatSimple(Number(formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)))} + {market?.collateralAsset?.symbol && ( 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 ' : ''}`} > ` 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 `font-sm` for text: +3. **Amount Columns**: Use flexbox with `justify-end` for right-aligned token icons, and use `text-sm` for text: ```tsx -
+
{amount}
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/ui/table.tsx b/src/components/ui/table.tsx index 3e51d605..fa298b74 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -15,7 +15,7 @@ Table.displayName = 'Table'; const TableHeader = forwardRef>(({ className, ...props }, ref) => (
)); @@ -24,7 +24,7 @@ TableHeader.displayName = 'TableHeader'; const TableBody = forwardRef>(({ className, ...props }, ref) => ( )); @@ -42,7 +42,7 @@ TableFooter.displayName = 'TableFooter'; const TableRow = forwardRef>(({ className, ...props }, ref) => ( )); @@ -60,7 +60,7 @@ TableHead.displayName = 'TableHead'; const TableCell = forwardRef>(({ className, ...props }, ref) => (
)); From 84a782fe4e04df7b9e270ed05109ad08959a3265 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Mon, 15 Dec 2025 23:47:47 +0800 Subject: [PATCH 7/7] misc: table style --- .../components/LiquidationsTable.tsx | 2 +- app/rewards/components/RewardTable.tsx | 44 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 28673f96..ca43bdc9 100644 --- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx @@ -128,7 +128,7 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) { {hasBadDebt ? (
- {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)} + {formatSimple(Number(formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)))} {market?.loanAsset?.symbol && ( - e.stopPropagation()} - > +
- {matchedToken.symbol} - + {matchedToken.symbol} +
- {getNetworkImg(tokenReward.asset.chain_id) ? ( - {`Chain - ) : ( -
- )} +
+ {networkImg && ( + network + )} + {networkName} +