diff --git a/.env.example b/.env.example
index d243f8d1e1..ca2813b50f 100644
--- a/.env.example
+++ b/.env.example
@@ -8,6 +8,8 @@ NEXT_PUBLIC_GOVERNANCE_CACHE_URL=https://governance-cache-api.aave.com/graphql
# Client on/off gate for gasless voting. The relay only works if VOTE_RELAY_URL and VOTE_RELAY_API_KEY are also set server-side.
NEXT_PUBLIC_ENABLE_GASLESS_VOTING=false
NEXT_PUBLIC_ENABLE_STAKING=true
+# Force-build the /dev/components showcase. Automatic on `next dev` and Vercel previews.
+NEXT_PUBLIC_ENABLE_DEV_PAGES=false
NEXT_PUBLIC_API_BASEURL=https://aave-api-v2.aave.com
NEXT_PUBLIC_TRANSAK_APP_URL=https://global.transak.com
NEXT_PUBLIC_TRANSAK_API_URL=https://api.transak.com
diff --git a/custom.d.ts b/custom.d.ts
index 923ce4a53f..0324420b5b 100644
--- a/custom.d.ts
+++ b/custom.d.ts
@@ -6,6 +6,7 @@ namespace NodeJS {
interface ProcessEnv {
NEXT_PUBLIC_ENABLE_GOVERNANCE: string;
NEXT_PUBLIC_ENABLE_STAKING: string;
+ NEXT_PUBLIC_ENABLE_DEV_PAGES?: string;
NEXT_PUBLIC_ENV: string;
NEXT_PUBLIC_API_BASEURL: string;
NEXT_PUBLIC_FORK_BASE_CHAIN_ID?: string;
diff --git a/next.config.js b/next.config.js
index 5ad0b01abc..5ed951774f 100644
--- a/next.config.js
+++ b/next.config.js
@@ -8,6 +8,17 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
const pageExtensions = ['page.tsx', 'ts'];
if (process.env.NEXT_PUBLIC_ENABLE_GOVERNANCE === 'true') pageExtensions.push('governance.tsx');
if (process.env.NEXT_PUBLIC_ENABLE_STAKING === 'true') pageExtensions.push('staking.tsx');
+// Component showcase at `/dev/components`. Its pages are named `*.dev.tsx`, so unless that
+// extension is registered here Next never sees them: no route, no bundle, a real 404 rather than a
+// blank page. On for `next dev` and Vercel preview builds; off for the production IPFS build, which
+// sets neither. A `VERCEL_ENV` of `production` vetoes it outright, so a dashboard variable left
+// scoped to every environment by mistake still can't leak the showcase into production.
+const enableDevPages =
+ process.env.VERCEL_ENV !== 'production' &&
+ (process.env.NEXT_PUBLIC_ENABLE_DEV_PAGES === 'true' ||
+ process.env.VERCEL_ENV === 'preview' ||
+ process.env.NODE_ENV === 'development');
+if (enableDevPages) pageExtensions.push('dev.tsx');
/** @type {import('next').NextConfig} */
module.exports = withSentryConfig(
diff --git a/pages/404.page.tsx b/pages/404.page.tsx
index 83a4a6dbe4..c3eb552506 100644
--- a/pages/404.page.tsx
+++ b/pages/404.page.tsx
@@ -44,7 +44,7 @@ export default function Aave404Page() {
We suggest you go back to the home page.
-
+
+
);
};
diff --git a/src/components/transactions/Swap/inputs/shared/NetworkSelector.tsx b/src/components/transactions/Swap/inputs/shared/NetworkSelector.tsx
index 5ce1bb177e..968318f191 100644
--- a/src/components/transactions/Swap/inputs/shared/NetworkSelector.tsx
+++ b/src/components/transactions/Swap/inputs/shared/NetworkSelector.tsx
@@ -1,13 +1,6 @@
-import { ChevronDownIcon } from '@heroicons/react/outline';
-import {
- Box,
- FormControl,
- MenuItem,
- Select,
- SelectChangeEvent,
- SvgIcon,
- Typography,
-} from '@mui/material';
+import { Box, Button, Menu, MenuItem, Typography } from '@mui/material';
+import { useState } from 'react';
+import { ChevronDownIcon } from 'src/components/icons/ChevronDownIcon';
import { MarketLogo } from 'src/components/MarketSwitcher';
import { SupportedNetworkWithChainId } from '../../helpers/shared/misc.helpers';
@@ -23,51 +16,57 @@ export const NetworkSelector = ({
selectedNetwork,
setSelectedNetwork,
}: NetworkSelectorProps) => {
- const handleChange = (event: SelectChangeEvent) => {
- setSelectedNetwork(Number(event.target.value));
- };
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+ const selected = networks.find((network) => network.chainId === selectedNetwork);
+
return (
-
-
-
+
+ >
);
};
diff --git a/src/components/transactions/Swap/inputs/shared/PriceInput.tsx b/src/components/transactions/Swap/inputs/shared/PriceInput.tsx
index 2548279bcd..71c0eddfb5 100644
--- a/src/components/transactions/Swap/inputs/shared/PriceInput.tsx
+++ b/src/components/transactions/Swap/inputs/shared/PriceInput.tsx
@@ -5,6 +5,7 @@ import React, { useEffect, useRef, useState } from 'react';
import NumberFormat, { NumberFormatProps } from 'react-number-format';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { ExternalTokenIcon } from 'src/components/primitives/TokenIcon';
+import { figSurfaceShadow } from 'src/utils/figmaColors';
import { SwappableToken, TokenType } from '../../types';
@@ -258,20 +259,21 @@ export const PriceInput = ({
return (
({
- border: `1px solid ${theme.palette.divider}`,
- borderRadius: '6px',
+ sx={{
+ borderRadius: '0.75rem',
+ boxShadow: figSurfaceShadow('shadow-stroke-1'),
overflow: 'hidden',
+ backgroundColor: 'bg-2',
px: 3,
py: 2,
width: '100%',
transition: 'background-color 0.15s ease',
'&:hover': {
- backgroundColor: 'background.surface',
+ backgroundColor: 'bg-2',
},
- })}
+ }}
>
-
+
When 1 {fromAsset.symbol} is worth:
@@ -330,8 +332,8 @@ export const PriceInput = ({
/>
{toAsset.symbol}
@@ -350,11 +352,11 @@ export const PriceInput = ({
width: 22,
height: 22,
borderRadius: '50%',
- backgroundColor: 'background.paper',
+ backgroundColor: 'surface-elevated',
ml: 1,
transition: 'background-color 0.2s ease',
'&:hover': {
- backgroundColor: 'background.surface',
+ backgroundColor: 'bg-2',
},
'&:hover .refresh-spin': {
transform: 'rotate(360deg)',
@@ -382,20 +384,20 @@ export const PriceInput = ({
value={rate.usd ? rate.usd.toString() : 0}
compact
symbol="USD"
- variant="secondary12"
- color="text.muted"
- symbolsColor="text.muted"
+ variant="subheader2"
+ color="fg-3"
+ symbolsColor="fg-3"
flexGrow={1}
/>
)}
-
+
diff --git a/src/components/transactions/Swap/inputs/shared/QuoteProgressRing.tsx b/src/components/transactions/Swap/inputs/shared/QuoteProgressRing.tsx
index cda959fde6..e9975ac497 100644
--- a/src/components/transactions/Swap/inputs/shared/QuoteProgressRing.tsx
+++ b/src/components/transactions/Swap/inputs/shared/QuoteProgressRing.tsx
@@ -1,5 +1,4 @@
import { Box, CircularProgress, SxProps } from '@mui/material';
-import { alpha, useTheme } from '@mui/material/styles';
import { useEffect, useMemo, useState } from 'react';
type QuoteProgressRingProps = {
@@ -21,7 +20,6 @@ export const QuoteProgressRing = ({
paused = false,
sx,
}: QuoteProgressRingProps) => {
- const theme = useTheme();
const [now, setNow] = useState(Date.now());
useEffect(() => {
@@ -41,8 +39,8 @@ export const QuoteProgressRing = ({
// Opacity from 0.25 to 1.0 based on progress
const ratio = Math.max(0, Math.min(1, progress / 100));
const opacity = 0.25 + 0.75 * ratio;
- return alpha(theme.palette.primary.main, opacity);
- }, [progress, theme]);
+ return `rgba(var(--mui-palette-primary-mainChannel) / ${opacity})`;
+ }, [progress]);
if (!active || !lastUpdatedAt || intervalMs <= 0) return null;
diff --git a/src/components/transactions/Swap/inputs/shared/SwitchRates.tsx b/src/components/transactions/Swap/inputs/shared/SwitchRates.tsx
index 9de8c9fe20..bc6e0698a2 100644
--- a/src/components/transactions/Swap/inputs/shared/SwitchRates.tsx
+++ b/src/components/transactions/Swap/inputs/shared/SwitchRates.tsx
@@ -52,8 +52,8 @@ export const SwitchRates = ({
visibleDecimals={0}
variant="main12"
symbol={isSwitched ? destSymbol : srcSymbol}
- symbolsVariant="secondary12"
- symbolsColor="text.secondary"
+ symbolsVariant="subheader2"
+ symbolsColor="fg-2"
value={'1'}
/>
diff --git a/src/components/transactions/Swap/inputs/shared/SwitchSlippageSelector.tsx b/src/components/transactions/Swap/inputs/shared/SwitchSlippageSelector.tsx
index 30799d5610..2a6b472004 100644
--- a/src/components/transactions/Swap/inputs/shared/SwitchSlippageSelector.tsx
+++ b/src/components/transactions/Swap/inputs/shared/SwitchSlippageSelector.tsx
@@ -1,19 +1,19 @@
import { CogIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
import {
+ Alert,
Box,
Button,
InputAdornment,
InputBase,
Menu,
SvgIcon,
- ToggleButton,
- ToggleButtonGroup,
Typography,
} from '@mui/material';
import { MouseEvent, useEffect, useState } from 'react';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
-import { Warning } from 'src/components/primitives/Warning';
+import { StyledTxModalToggleButton } from 'src/components/StyledToggleButton';
+import { StyledTxModalToggleGroup } from 'src/components/StyledToggleButtonGroup';
import { ValidationData } from '../../helpers/shared/slippage.helpers';
@@ -123,7 +123,7 @@ export const SwitchSlippageSelector = ({
return (
-
+
{isCustomSlippage ? (
Custom slippage
) : provider === 'paraswap' ? (
@@ -157,32 +157,18 @@ export const SwitchSlippageSelector = ({
Max slippage
- handlePresetSlippageChange(value)}
+ onChange={(_, value) => value && handlePresetSlippageChange(value)}
+ // Compact menu footprint, sized to the custom-slippage input beside it; the
+ // shell/pill treatment comes from the shared control.
+ sx={{ width: 'auto', height: '28px' }}
>
{slippageOptions.map((option) => (
-
+
{isNaN(Number(option)) ? (
-
+
{provider === 'paraswap' ? Default : Auto}
) : (
@@ -191,13 +177,11 @@ export const SwitchSlippageSelector = ({
visibleDecimals={2}
symbol="%"
variant="subheader2"
- color="primary.main"
- symbolsColor="primary.main"
/>
)}
-
+
))}
-
+
-
+
%
@@ -216,18 +200,16 @@ export const SwitchSlippageSelector = ({
width: '120px',
border: 1,
borderWidth: '1px',
- backgroundColor: 'background.surface',
- borderColor: slippageValidation
- ? `${slippageValidation.severity}.main`
- : 'background.surface',
+ backgroundColor: 'bg-2',
+ borderColor: slippageValidation ? `${slippageValidation.severity}.main` : 'bg-2',
borderRadius: '4px',
}}
/>
{slippageValidation && (
-
+
{slippageValidation.message}
-
+
)}
@@ -252,7 +234,7 @@ export const SwitchSlippageSelector = ({
>
{
>
) : (
-
+ Please connect your wallet to swap collateral. close()} />
diff --git a/src/components/transactions/Swap/modals/DebtSwapModal.tsx b/src/components/transactions/Swap/modals/DebtSwapModal.tsx
index c684129b4d..d0ce49785c 100644
--- a/src/components/transactions/Swap/modals/DebtSwapModal.tsx
+++ b/src/components/transactions/Swap/modals/DebtSwapModal.tsx
@@ -25,7 +25,7 @@ export const DebtSwapModal = () => {
>
) : (
-
+ Please connect your wallet to swap debt. close()} />
diff --git a/src/components/transactions/Swap/modals/SwapModal.tsx b/src/components/transactions/Swap/modals/SwapModal.tsx
index b17fe507f2..e51c353a72 100644
--- a/src/components/transactions/Swap/modals/SwapModal.tsx
+++ b/src/components/transactions/Swap/modals/SwapModal.tsx
@@ -23,7 +23,7 @@ export const SwapModal = () => {
>
) : (
-
+ Please connect your wallet to swap tokens. close()} />
diff --git a/src/components/transactions/Swap/modals/request/NoEligibleAssetsToSwap.tsx b/src/components/transactions/Swap/modals/request/NoEligibleAssetsToSwap.tsx
index aac778659d..3584cb99d1 100644
--- a/src/components/transactions/Swap/modals/request/NoEligibleAssetsToSwap.tsx
+++ b/src/components/transactions/Swap/modals/request/NoEligibleAssetsToSwap.tsx
@@ -3,7 +3,7 @@ import { Typography } from '@mui/material';
export const NoEligibleAssetsToSwap = () => {
return (
-
+ No eligible assets to swap.
);
diff --git a/src/components/transactions/Swap/modals/result/CowOrderToast.tsx b/src/components/transactions/Swap/modals/result/CowOrderToast.tsx
index 930f25f093..a3360bc757 100644
--- a/src/components/transactions/Swap/modals/result/CowOrderToast.tsx
+++ b/src/components/transactions/Swap/modals/result/CowOrderToast.tsx
@@ -1,18 +1,16 @@
-import { useTheme } from '@mui/material';
import { Toaster } from 'sonner';
+import { figVars } from 'src/utils/figmaColors';
export const CowOrderToast = () => {
- const theme = useTheme();
-
return (
diff --git a/src/components/transactions/Swap/modals/result/SwapResultView.tsx b/src/components/transactions/Swap/modals/result/SwapResultView.tsx
index 83d7a734ad..f82b3665f3 100644
--- a/src/components/transactions/Swap/modals/result/SwapResultView.tsx
+++ b/src/components/transactions/Swap/modals/result/SwapResultView.tsx
@@ -59,17 +59,17 @@ export const SwapWithSurplusTooltip = ({
<>
- Base:
+ Base:
- Surplus: {' '}
- (
+ Surplus:{' '}
+ (
)
@@ -260,7 +260,7 @@ export const SwapTxSuccessView = ({
size={20}
sx={{
mr: 1,
- color: (theme) => theme.palette.grey[400],
+ color: (theme) => theme.vars.palette.grey[400],
}}
/>
Details will be available soon
@@ -276,7 +276,7 @@ export const SwapTxSuccessView = ({
customExplorerLinkText={customExplorerLinkText}
>
-
+
{provider === 'cowprotocol' ? (
<>
{orderStatus === 'open' ? (
@@ -301,17 +301,17 @@ export const SwapTxSuccessView = ({
-
+
{provider == 'cowprotocol' &&
((orderStatus == 'open' && !isNativeToken(symbol)) || orderStatus == 'failed')
? `${resultScreenTokensFromTitle ?? 'Send'}`
@@ -327,7 +327,7 @@ export const SwapTxSuccessView = ({
/>
+
{inAmount} {symbol}
}
@@ -345,14 +345,14 @@ export const SwapTxSuccessView = ({
-
+
{symbol}
-
+
{provider == 'cowprotocol' && (orderStatus == 'open' || orderStatus == 'failed')
? `${resultScreenTokensToTitle ?? 'Receive'}`
: `${resultScreenTokensToTitle ?? 'Received'}`}
@@ -367,7 +367,7 @@ export const SwapTxSuccessView = ({
/>
+
{outFinalAmount} {outSymbol}
}
@@ -385,7 +385,7 @@ export const SwapTxSuccessView = ({
-
+
{outSymbol}
@@ -394,7 +394,7 @@ export const SwapTxSuccessView = ({
{surplusDisplay}
@@ -403,15 +403,15 @@ export const SwapTxSuccessView = ({
-
+
Swap saved in your{' '}
-
+ Market
@@ -35,7 +35,7 @@ export function OrderTypeSelector({
value={OrderType.LIMIT}
disabled={switchType === OrderType.LIMIT || limitsOrderButtonBlocked}
>
-
+ Limit
diff --git a/src/components/transactions/Swap/warnings/postInputs/CowAdapterApprovalInfo.tsx b/src/components/transactions/Swap/warnings/postInputs/CowAdapterApprovalInfo.tsx
index c86701fc11..606fb950b1 100644
--- a/src/components/transactions/Swap/warnings/postInputs/CowAdapterApprovalInfo.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/CowAdapterApprovalInfo.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { useModalContext } from 'src/hooks/useModal';
import { SwapState } from '../../types';
@@ -19,13 +18,11 @@ export function CowAdapterApprovalInfo({ state }: { state: SwapState }) {
if (!isCow || !isAdapterFlow || approvalTxState?.success || !isFlashloan) return null;
return (
-
-
-
- A temporary contract will be used to execute the trade. Your wallet may show a warning for
- approving a new or empty address.
-
-
-
+
+
+ A temporary contract will be used to execute the trade. Your wallet may show a warning for
+ approving a new or empty address.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/CustomTokenWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/CustomTokenWarning.tsx
index 65e83056f4..cfee99c692 100644
--- a/src/components/transactions/Swap/warnings/postInputs/CustomTokenWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/CustomTokenWarning.tsx
@@ -1,5 +1,4 @@
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { SwapState, TokenType } from '../../types';
@@ -14,10 +13,8 @@ export function CustomTokenWarning({ state }: { state: SwapState }) {
}
return (
-
-
- You selected a custom imported token. Make sure it's the right token.
-
-
+
+ You selected a custom imported token. Make sure it's the right token.
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/GasEstimationWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/GasEstimationWarning.tsx
index 285d993a39..efe2bfed86 100644
--- a/src/components/transactions/Swap/warnings/postInputs/GasEstimationWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/GasEstimationWarning.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { SwapState } from '../../types';
@@ -14,12 +13,10 @@ export function GasEstimationWarning({ state }: { state: SwapState }) {
if (!hasGasEstimationWarning) return null;
return (
-
-
-
- The swap could not be completed. Try increasing slippage or changing the amount.
-
-
-
+
+
+ The swap could not be completed. Try increasing slippage or changing the amount.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/HighCostsLimitOrderWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/HighCostsLimitOrderWarning.tsx
index 91a86d91ae..b00083336e 100644
--- a/src/components/transactions/Swap/warnings/postInputs/HighCostsLimitOrderWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/HighCostsLimitOrderWarning.tsx
@@ -1,8 +1,7 @@
import { valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
+import { Alert } from '@mui/material';
import { useEffect, useMemo } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { ActionsBlockedReason, OrderType, SwapState } from '../../types';
@@ -92,13 +91,11 @@ export function HighCostsLimitOrderWarning({
return null;
return (
-
-
-
- Estimated costs are {costsPercentOfSell.toFixed(2)}% of the sell amount. This order is
- unlikely to be filled.
-
-
-
+
+
+ Estimated costs are {costsPercentOfSell.toFixed(2)}% of the sell amount. This order is
+ unlikely to be filled.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/HighPriceImpactWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/HighPriceImpactWarning.tsx
index 29a282c8a6..471c1c3b3e 100644
--- a/src/components/transactions/Swap/warnings/postInputs/HighPriceImpactWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/HighPriceImpactWarning.tsx
@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro';
-import { Box, Checkbox, Typography } from '@mui/material';
+import { Alert, Box, Checkbox } from '@mui/material';
import { Dispatch, useEffect, useMemo, useState } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { SwapInputChanges } from '../../analytics/constants';
import { useHandleAnalytics } from '../../analytics/useTrackAnalytics';
@@ -54,43 +53,32 @@ export function HighPriceImpactWarning({
if (actionsBlockedReasonsAmount(state) > 1) return null;
return (
- 0.3 ? 'error' : 'warning'}
- icon={false}
+ data-size="small"
sx={{
+ width: '100%',
mt: 2,
mb: 2,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
}}
>
-
-
- High price impact ({(lostValue * 100).toFixed(1)}%)! This route will
- return {state.isInvertedSwap ? 'more' : 'less'} due to low liquidity or small order size.
-
-
-
-
- Please review the swap values before confirming.
-
-
+
+ High price impact ({(lostValue * 100).toFixed(1)}%)! This route will return{' '}
+ {state.isInvertedSwap ? 'more' : 'less'} due to low liquidity or small order size.
+ {' '}
+ Please review the swap values before confirming.
{requireConfirmation && (
-
-
- I confirm the swap knowing that I could lose up to{' '}
- {(lostValue * 100).toFixed(0)}% on this swap.
-
-
+
+ I confirm the swap knowing that I could lose up to{' '}
+ {(lostValue * 100).toFixed(0)}% on this swap.
+ {
@@ -102,10 +90,11 @@ export function HighPriceImpactWarning({
);
}}
size="small"
+ sx={{ p: 0, ml: 2 }}
data-cy={'high-price-impact-checkbox'}
/>
)}
-
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/LimitOrderAmountWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/LimitOrderAmountWarning.tsx
index ebd524c6f1..9878dedcda 100644
--- a/src/components/transactions/Swap/warnings/postInputs/LimitOrderAmountWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/LimitOrderAmountWarning.tsx
@@ -1,8 +1,7 @@
import { valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
+import { Alert } from '@mui/material';
import { useMemo } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { SwapState } from '../../types';
import { OrderType } from '../../types/shared.types';
@@ -66,24 +65,20 @@ export function LimitOrderAmountWarning({ state }: { state: SwapState }) {
if (!shouldShowWarning) return null;
return (
-
-
-
- Your order amounts are {isHigherDifference ? 'significantly ' : ''} less favorable by{' '}
- {differencePercentage?.abs()?.toFixed(1) ?? '0'}% to the liquidity provider than
- recommended. This order may not be executed.
-
-
-
+
+ Your order amounts are {isHigherDifference ? 'significantly ' : ''} less favorable by{' '}
+ {differencePercentage?.abs()?.toFixed(1) ?? '0'}% to the liquidity provider than
+ recommended. This order may not be executed.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/LiquidationCriticalWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/LiquidationCriticalWarning.tsx
index b4ea0ff153..ad02155f4e 100644
--- a/src/components/transactions/Swap/warnings/postInputs/LiquidationCriticalWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/LiquidationCriticalWarning.tsx
@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
+import { Alert } from '@mui/material';
import { Dispatch } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { SwapParams, SwapState } from '../../types';
@@ -14,23 +13,20 @@ export function LiquidationCriticalWarning({
}) {
// TODO: move to be an error not a warning and remove isLiquidatable from state.
return (
-
-
-
- Your health factor after this swap will be critically low and may result in liquidation.
- Please choose a different asset or reduce the swap amount to stay safe.
-
-
-
+
+ Your health factor after this swap will be critically low and may result in liquidation.
+ Please choose a different asset or reduce the swap amount to stay safe.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/LowHealthFactorWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/LowHealthFactorWarning.tsx
index 9e3d3f0044..962ff81835 100644
--- a/src/components/transactions/Swap/warnings/postInputs/LowHealthFactorWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/LowHealthFactorWarning.tsx
@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro';
-import { Box, Checkbox, Typography } from '@mui/material';
+import { Alert, Box, Checkbox } from '@mui/material';
import { Dispatch, useEffect, useState } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { ActionsBlockedReason, SwapParams, SwapState } from '../../types';
import { shouldRequireConfirmationHFlow } from '../helpers';
@@ -40,44 +39,30 @@ export function LowHealthFactorWarning({
}
return (
-
-
-
- Low health factor after swap. Your position will carry a higher risk of liquidation.
-
-
+
+
+ Low health factor after swap. Your position will carry a higher risk of liquidation.
+
{!state.actionsBlocked[ActionsBlockedReason.IS_LIQUIDATABLE] && (
-
- I understand the liquidation risk and want to proceed
-
+ I understand the liquidation risk and want to proceed {
setLowHFConfirmed(!lowHFConfirmed);
}}
size="small"
+ sx={{ p: 0, ml: 2 }}
data-cy={'low-hf-checkbox'}
/>
)}
-
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/SafetyModuleSwapWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/SafetyModuleSwapWarning.tsx
index c2f655ca4c..8e83d73dc7 100644
--- a/src/components/transactions/Swap/warnings/postInputs/SafetyModuleSwapWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/SafetyModuleSwapWarning.tsx
@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
+import { Alert } from '@mui/material';
import { Link } from 'src/components/primitives/Link';
-import { Warning } from 'src/components/primitives/Warning';
import { SwapState } from '../../types';
import { SAFETY_MODULE_TOKENS } from '../constants';
@@ -13,16 +12,14 @@ export function SafetyModuleSwapWarning({ state }: { state: SwapState }) {
if (!isSwappingSafetyModuleToken) return null;
return (
-
-
-
- For swapping safety module assets please unstake your position{' '}
- close()}>
- here
-
- .
-
-
-
+
+
+ For swapping safety module assets please unstake your position{' '}
+ close()}>
+ here
+
+ .
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/ShieldSwapWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/ShieldSwapWarning.tsx
index 92c6d6f409..ef875b1969 100644
--- a/src/components/transactions/Swap/warnings/postInputs/ShieldSwapWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/ShieldSwapWarning.tsx
@@ -1,8 +1,6 @@
-import { ShieldExclamationIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import { Box, SvgIcon, Typography } from '@mui/material';
+import { Alert, AlertTitle } from '@mui/material';
import { Dispatch, useEffect, useMemo } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { useRootStore } from 'src/store/root';
import { ActionsBlockedReason, SwapState } from '../../types';
@@ -42,31 +40,14 @@ export function ShieldSwapWarning({
if (!shouldBlock) return null;
return (
-
-
-
-
-
-
- Aave Shield: Transaction blocked
-
-
-
-
- This swap has a price impact of {(lostValue * 100).toFixed(1)}%, which exceeds the 25%
- safety threshold. To proceed, disable Aave Shield in the settings menu.
-
-
-
+
+
+ Aave Shield: Transaction blocked
+
+
+ This swap has a price impact of {(lostValue * 100).toFixed(1)}%, which exceeds the 25%
+ safety threshold. To proceed, disable Aave Shield in the settings menu.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/SlippageWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/SlippageWarning.tsx
index 7131b65fc3..1e4fd33a92 100644
--- a/src/components/transactions/Swap/warnings/postInputs/SlippageWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/SlippageWarning.tsx
@@ -1,5 +1,4 @@
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { OrderType, SwapState } from '../../types';
@@ -8,10 +7,8 @@ export function SlippageWarning({ state }: { state: SwapState }) {
if (state.orderType === OrderType.LIMIT) return null;
return (
-
-
- Slippage is lower than recommended. The swap may be delayed or fail.
-
-
+
+ Slippage is lower than recommended. The swap may be delayed or fail.
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/USDTResetWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/USDTResetWarning.tsx
index 1c8f421169..82f7648fd2 100644
--- a/src/components/transactions/Swap/warnings/postInputs/USDTResetWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/USDTResetWarning.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { SwapState } from '../../types';
@@ -8,13 +7,11 @@ export function USDTResetWarning({ state }: { state: SwapState }) {
if (!state.requiresApprovalReset) return null;
return (
-
-
-
- USDT on Ethereum requires approval reset before a new approval. This will require an
- additional transaction.
-
-
-
+
+
+ USDT on Ethereum requires approval reset before a new approval. This will require an
+ additional transaction.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/postInputs/ZeroLTVDestinationWarning.tsx b/src/components/transactions/Swap/warnings/postInputs/ZeroLTVDestinationWarning.tsx
index 18ed8bcadf..2de4487fcf 100644
--- a/src/components/transactions/Swap/warnings/postInputs/ZeroLTVDestinationWarning.tsx
+++ b/src/components/transactions/Swap/warnings/postInputs/ZeroLTVDestinationWarning.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
import { hasNonZeroEffectiveLtv } from 'src/utils/hfUtils';
@@ -37,13 +36,11 @@ export function ZeroLTVDestinationWarning({ state }: { state: SwapState }) {
}
return (
-
-
-
- {destinationReserve.symbol} has a Loan-to-Value of 0, so it will not be enabled as
- collateral automatically after the swap.
-
-
-
+
+
+ {destinationReserve.symbol} has a Loan-to-Value of 0, so it will not be enabled as
+ collateral automatically after the swap.
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/preInputs/CowOpenOrdersWarning.tsx b/src/components/transactions/Swap/warnings/preInputs/CowOpenOrdersWarning.tsx
index 21ba7cf07c..d5d40c31bf 100644
--- a/src/components/transactions/Swap/warnings/preInputs/CowOpenOrdersWarning.tsx
+++ b/src/components/transactions/Swap/warnings/preInputs/CowOpenOrdersWarning.tsx
@@ -1,8 +1,7 @@
import { normalize } from '@aave/math-utils';
import { OrderStatus } from '@cowprotocol/cow-sdk';
-import { Link, Typography } from '@mui/material';
+import { Alert, Link } from '@mui/material';
import { useEffect, useState } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { useSwapOrdersTracking } from 'src/hooks/useSwapOrdersTracking';
import { useRootStore } from 'src/store/root';
import { findByChainId } from 'src/ui-config/marketsConfig';
@@ -63,20 +62,18 @@ export function CowOpenOrdersWarning({ state }: { state: SwapState }) {
if (!cowOpenOrdersTotalAmountFormatted && !hasActiveForToken) return null;
return (
-
-
- {cowOpenOrdersTotalAmountFormatted ? (
- <>
- You have open orders for {cowOpenOrdersTotalAmountFormatted} {state.sourceToken.symbol}.{' '}
- >
- ) : (
- <>You have in-progress swaps for {state.sourceToken.symbol}. >
- )}
- Track them in your{' '}
-
- transaction history
-
-
-
+
+ {cowOpenOrdersTotalAmountFormatted ? (
+ <>
+ You have open orders for {cowOpenOrdersTotalAmountFormatted} {state.sourceToken.symbol}.{' '}
+ >
+ ) : (
+ <>You have in-progress swaps for {state.sourceToken.symbol}. >
+ )}
+ Track them in your{' '}
+
+ transaction history
+
+
);
}
diff --git a/src/components/transactions/Swap/warnings/preInputs/NativeLimitOrderInfo.tsx b/src/components/transactions/Swap/warnings/preInputs/NativeLimitOrderInfo.tsx
index 3cdd52ac6d..0ea8e5d745 100644
--- a/src/components/transactions/Swap/warnings/preInputs/NativeLimitOrderInfo.tsx
+++ b/src/components/transactions/Swap/warnings/preInputs/NativeLimitOrderInfo.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
import { SwapParams, SwapProvider, SwapState, SwapType, TokenType } from '../../types';
@@ -13,13 +12,11 @@ export function NativeLimitOrderInfo({ state, params }: { state: SwapState; para
if (!isClassicSwap || !isNativeInput || !isCoWProtocol) return null;
return (
-
-
-
- For security reasons, limit orders are not supported for Native tokens. To place a limit
- order, use the wrapped version.
-
-
-
+
+
+ For security reasons, limit orders are not supported for Native tokens. To place a limit
+ order, use the wrapped version.
+
+
);
}
diff --git a/src/components/transactions/TxActionsWrapper.tsx b/src/components/transactions/TxActionsWrapper.tsx
index 7f89da0206..6aa2b7c0b4 100644
--- a/src/components/transactions/TxActionsWrapper.tsx
+++ b/src/components/transactions/TxActionsWrapper.tsx
@@ -197,7 +197,7 @@ export const TxActionsWrapper = ({
{content}
{readOnlyModeAddress && (
-
+ Read-only mode. Connect to a wallet to perform transactions.
)}
diff --git a/src/components/transactions/Warnings/AAVEWarning.tsx b/src/components/transactions/Warnings/AAVEWarning.tsx
index 727d945e88..6c4f6cd91b 100644
--- a/src/components/transactions/Warnings/AAVEWarning.tsx
+++ b/src/components/transactions/Warnings/AAVEWarning.tsx
@@ -1,20 +1,17 @@
import { Trans } from '@lingui/macro';
-import { Link, Typography } from '@mui/material';
+import { Alert, Link } from '@mui/material';
import { ROUTES } from '../../primitives/Link';
-import { Warning } from '../../primitives/Warning';
export const AAVEWarning = () => {
return (
-
-
- Supplying your AAVE{' '}
- tokens is not the same as staking them. If you wish to stake your AAVE{' '}
- tokens, please go to the {' '}
-
- staking view
-
-
-
+
+ Supplying your AAVE{' '}
+ tokens is not the same as staking them. If you wish to stake your AAVE{' '}
+ tokens, please go to the {' '}
+
+ staking view
+
+
);
};
diff --git a/src/components/transactions/Warnings/BorrowCapWarning.tsx b/src/components/transactions/Warnings/BorrowCapWarning.tsx
index 204c06b4b4..a009bb560c 100644
--- a/src/components/transactions/Warnings/BorrowCapWarning.tsx
+++ b/src/components/transactions/Warnings/BorrowCapWarning.tsx
@@ -1,16 +1,16 @@
import { Trans } from '@lingui/macro';
-import { AlertProps } from '@mui/material';
+import { Alert, AlertProps } from '@mui/material';
import { AssetCapData } from 'src/hooks/useAssetCaps';
import { Link } from '../../primitives/Link';
-import { Warning } from '../../primitives/Warning';
type BorrowCapWarningProps = AlertProps & {
borrowCap: AssetCapData;
icon?: boolean;
};
-export const BorrowCapWarning = ({ borrowCap, icon = true, ...rest }: BorrowCapWarningProps) => {
+// `icon` is destructured only to keep it out of `...rest` (the alert always shows its severity icon).
+export const BorrowCapWarning = ({ borrowCap, icon, ...rest }: BorrowCapWarningProps) => {
// Don't show a warning when less than 98% utilized
if (!borrowCap.percentUsed || borrowCap.percentUsed < 98) return null;
@@ -27,11 +27,11 @@ export const BorrowCapWarning = ({ borrowCap, icon = true, ...rest }: BorrowCapW
};
return (
-
+
{renderText()}{' '}
Learn more
-
+
);
};
diff --git a/src/components/transactions/Warnings/ChangeNetworkWarning.tsx b/src/components/transactions/Warnings/ChangeNetworkWarning.tsx
index 7f71b1f56c..1ebd5adae7 100644
--- a/src/components/transactions/Warnings/ChangeNetworkWarning.tsx
+++ b/src/components/transactions/Warnings/ChangeNetworkWarning.tsx
@@ -1,14 +1,12 @@
import { ChainId } from '@aave/contract-helpers';
import { Trans } from '@lingui/macro';
-import { AlertProps, Button, CircularProgress, Typography } from '@mui/material';
+import { Alert, AlertProps, Button, CircularProgress } from '@mui/material';
import { useEffect, useState } from 'react';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { TrackEventProps } from 'src/store/analyticsSlice';
import { useRootStore } from 'src/store/root';
import { GENERAL } from 'src/utils/events';
-import { Warning } from '../../primitives/Warning';
-
export type ChangeNetworkWarningProps = AlertProps & {
funnel?: string;
networkName: string;
@@ -25,6 +23,7 @@ export const ChangeNetworkWarning = ({
funnel,
askManualSwitch = false,
autoSwitchOnMount = true,
+ sx,
...rest
}: ChangeNetworkWarningProps) => {
const { switchNetwork, switchNetworkError } = useWeb3Context();
@@ -70,46 +69,38 @@ export const ChangeNetworkWarning = ({
switchNetwork(chainId);
};
return (
-
{isAutoSwitching ? (
-
+ <>
Switching to {networkName}...
-
+ >
) : switchNetworkError ? (
-
-
- {hasAttemptedAutoSwitch
- ? "We couldn't switch the network automatically. Please check if you can change it from the wallet."
- : "Seems like we can't switch the network automatically. Please check if you can change it from the wallet."}
-
-
+
+ {hasAttemptedAutoSwitch
+ ? "We couldn't switch the network automatically. Please check if you can change it from the wallet."
+ : "Seems like we can't switch the network automatically. Please check if you can change it from the wallet."}
+
) : (
// Show manual switch option
-
+ <>
{hasAttemptedAutoSwitch
? `Auto-switch failed. Please manually switch to ${networkName}.`
: `Please switch to ${networkName}.`}
{' '}
{!askManualSwitch && (
-
-
- Switch Network
-
+
+ Switch Network
)}
-
+ >
)}
-
+
);
};
diff --git a/src/components/transactions/Warnings/CowLowerThanMarketWarning.tsx b/src/components/transactions/Warnings/CowLowerThanMarketWarning.tsx
index 504b906e23..685a9ae7a4 100644
--- a/src/components/transactions/Warnings/CowLowerThanMarketWarning.tsx
+++ b/src/components/transactions/Warnings/CowLowerThanMarketWarning.tsx
@@ -1,15 +1,12 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
export const CowLowerThanMarketWarning = () => {
return (
-
-
-
- The selected rate is lower than the market price. You might incur a loss if you proceed.
-
-
-
+
+
+ The selected rate is lower than the market price. You might incur a loss if you proceed.
+
+
);
};
diff --git a/src/components/transactions/Warnings/DebtCeilingWarning.tsx b/src/components/transactions/Warnings/DebtCeilingWarning.tsx
index 84c7add649..01cac5b3f3 100644
--- a/src/components/transactions/Warnings/DebtCeilingWarning.tsx
+++ b/src/components/transactions/Warnings/DebtCeilingWarning.tsx
@@ -1,20 +1,16 @@
import { Trans } from '@lingui/macro';
-import { AlertProps } from '@mui/material';
+import { Alert, AlertProps } from '@mui/material';
import { AssetCapData } from 'src/hooks/useAssetCaps';
import { Link } from '../../primitives/Link';
-import { Warning } from '../../primitives/Warning';
type DebtCeilingWarningProps = AlertProps & {
debtCeiling: AssetCapData;
icon?: boolean;
};
-export const DebtCeilingWarning = ({
- debtCeiling,
- icon = true,
- ...rest
-}: DebtCeilingWarningProps) => {
+// `icon` is destructured only to keep it out of `...rest` (the alert always shows its severity icon).
+export const DebtCeilingWarning = ({ debtCeiling, icon, ...rest }: DebtCeilingWarningProps) => {
// Don't show a warning when less than 98% utilized
if (!debtCeiling.percentUsed || debtCeiling.percentUsed < 98) return null;
@@ -35,7 +31,7 @@ export const DebtCeilingWarning = ({
};
return (
-
+
{renderText()}{' '}
Learn more
-
+
);
};
diff --git a/src/components/transactions/Warnings/IsolationModeWarning.tsx b/src/components/transactions/Warnings/IsolationModeWarning.tsx
index c7f486a855..7294c599dd 100644
--- a/src/components/transactions/Warnings/IsolationModeWarning.tsx
+++ b/src/components/transactions/Warnings/IsolationModeWarning.tsx
@@ -1,8 +1,7 @@
import { Trans } from '@lingui/macro';
-import { AlertColor, Typography } from '@mui/material';
+import { Alert, AlertColor, AlertTitle } from '@mui/material';
import { Link } from '../../primitives/Link';
-import { Warning } from '../../primitives/Warning';
interface IsolationModeWarningProps {
asset?: string;
@@ -11,18 +10,16 @@ interface IsolationModeWarningProps {
export const IsolationModeWarning = ({ asset, severity }: IsolationModeWarningProps) => {
return (
-
-
+
+ You are entering Isolation mode
-
-
-
- In Isolation mode, you cannot supply other assets as collateral. A global debt ceiling
- limits the borrowing power of the isolated asset. To exit isolation mode disable{' '}
- {asset ? asset : ''} as collateral before borrowing another asset. Read more in our{' '}
- FAQ
-
-
-
+
+
+ In Isolation mode, you cannot supply other assets as collateral. A global debt ceiling
+ limits the borrowing power of the isolated asset. To exit isolation mode disable{' '}
+ {asset ? asset : ''} as collateral before borrowing another asset. Read more in our{' '}
+ FAQ
+
+
);
};
diff --git a/src/components/transactions/Warnings/MarketWarning.tsx b/src/components/transactions/Warnings/MarketWarning.tsx
index b2e1a7b26a..ff403ab640 100644
--- a/src/components/transactions/Warnings/MarketWarning.tsx
+++ b/src/components/transactions/Warnings/MarketWarning.tsx
@@ -1,7 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Link, Typography } from '@mui/material';
-
-import { Warning } from '../../primitives/Warning';
+import { Alert, Link } from '@mui/material';
const WarningMessage = ({ market }: { market: string }) => {
if (market) {
@@ -27,13 +25,11 @@ interface MarketWarningProps {
// NOTE: Deprecated for now as no frozen markets
export const MarketWarning = ({ marketName, forum }: MarketWarningProps) => {
return (
-
-
- {' '}
-
- {forum ? Join the community discussion : Learn more}
-
-
-
+
+ {' '}
+
+ {forum ? Join the community discussion : Learn more}
+
+
);
};
diff --git a/src/components/transactions/Warnings/ParaswapErrorDisplay.tsx b/src/components/transactions/Warnings/ParaswapErrorDisplay.tsx
index fa1df54442..b0ea78b9be 100644
--- a/src/components/transactions/Warnings/ParaswapErrorDisplay.tsx
+++ b/src/components/transactions/Warnings/ParaswapErrorDisplay.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Box, Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert, Box } from '@mui/material';
import { TxErrorType } from 'src/ui-config/errorMapping';
import { GasEstimationError } from '../FlowCommons/GasEstimationError';
@@ -18,12 +17,9 @@ export const ParaswapErrorDisplay: React.FC = ({ txError }) => {
{txError.rawError.message !== USER_DENIED_SIGNATURE &&
txError.rawError.message !== USER_DENIED_TRANSACTION && (
-
-
- {' '}
- Tip: Try increasing slippage or reduce input amount
-
-
+
+ Tip: Try increasing slippage or reduce input amount
+
)}
diff --git a/src/components/transactions/Warnings/SNXWarning.tsx b/src/components/transactions/Warnings/SNXWarning.tsx
index 1574614eb5..63981c7e21 100644
--- a/src/components/transactions/Warnings/SNXWarning.tsx
+++ b/src/components/transactions/Warnings/SNXWarning.tsx
@@ -1,19 +1,15 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-
-import { Warning } from '../../primitives/Warning';
+import { Alert } from '@mui/material';
export const SNXWarning = () => {
return (
-
-
- Before supplying SNX{' '}
-
- {' '}
- please check that the amount you want to supply is not currently being used for staking.
- If it is being used for staking, your transaction might fail.
-
-
-
+
+ Before supplying SNX{' '}
+
+ {' '}
+ please check that the amount you want to supply is not currently being used for staking. If
+ it is being used for staking, your transaction might fail.
+
+
);
};
diff --git a/src/components/transactions/Warnings/SupplyCapWarning.tsx b/src/components/transactions/Warnings/SupplyCapWarning.tsx
index 34c4c9ad33..326d177281 100644
--- a/src/components/transactions/Warnings/SupplyCapWarning.tsx
+++ b/src/components/transactions/Warnings/SupplyCapWarning.tsx
@@ -1,16 +1,16 @@
import { Trans } from '@lingui/macro';
-import { AlertProps } from '@mui/material';
+import { Alert, AlertProps } from '@mui/material';
import { AssetCapData } from 'src/hooks/useAssetCaps';
import { Link } from '../../primitives/Link';
-import { Warning } from '../../primitives/Warning';
type SupplyCapWarningProps = AlertProps & {
supplyCap: AssetCapData;
icon?: boolean;
};
-export const SupplyCapWarning = ({ supplyCap, icon = true, ...rest }: SupplyCapWarningProps) => {
+// `icon` is destructured only to keep it out of `...rest` (the alert always shows its severity icon).
+export const SupplyCapWarning = ({ supplyCap, icon, ...rest }: SupplyCapWarningProps) => {
// Don't show a warning when less than 98% utilized
if (!supplyCap.percentUsed || supplyCap.percentUsed < 98) return null;
@@ -28,11 +28,11 @@ export const SupplyCapWarning = ({ supplyCap, icon = true, ...rest }: SupplyCapW
};
return (
-
+
{renderText()}{' '}
Learn more
-
+
);
};
diff --git a/src/components/transactions/Warnings/USDTResetWarning.tsx b/src/components/transactions/Warnings/USDTResetWarning.tsx
index dfd320b172..35585116de 100644
--- a/src/components/transactions/Warnings/USDTResetWarning.tsx
+++ b/src/components/transactions/Warnings/USDTResetWarning.tsx
@@ -1,16 +1,13 @@
import { Trans } from '@lingui/macro';
-import { Typography } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert } from '@mui/material';
export const USDTResetWarning = () => {
return (
-
-
-
- USDT on Ethereum requires approval reset before a new approval. This will require an
- additional transaction.
-
-
-
+
+
+ USDT on Ethereum requires approval reset before a new approval. This will require an
+ additional transaction.
+
+
);
};
diff --git a/src/components/transactions/Withdraw/WithdrawModalContent.tsx b/src/components/transactions/Withdraw/WithdrawModalContent.tsx
index bc427401f9..65ec8542bb 100644
--- a/src/components/transactions/Withdraw/WithdrawModalContent.tsx
+++ b/src/components/transactions/Withdraw/WithdrawModalContent.tsx
@@ -1,9 +1,8 @@
import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers';
import { valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, Checkbox, Typography } from '@mui/material';
+import { Alert, Box, Checkbox, Typography } from '@mui/material';
import { useRef, useState } from 'react';
-import { Warning } from 'src/components/primitives/Warning';
import { ExtendedFormattedUser } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useModalContext } from 'src/hooks/useModal';
import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw';
@@ -188,12 +187,12 @@ export const WithdrawModalContent = ({
{displayRiskCheckbox && (
<>
-
+
Withdrawing this amount will reduce your health factor and increase risk of
liquidation.
-
+
-
+ Withdraw
@@ -49,7 +49,7 @@ export function WithdrawTypeSelector({
trackEvent(WITHDRAW_MODAL.SWITCH_WITHDRAW_TYPE, { withdrawType: 'Withdraw and Swap' })
}
>
-
+ Withdraw & Swap
diff --git a/src/hooks/useConnectGate.ts b/src/hooks/useConnectGate.ts
new file mode 100644
index 0000000000..d766560f87
--- /dev/null
+++ b/src/hooks/useConnectGate.ts
@@ -0,0 +1,21 @@
+import { useModal } from 'connectkit';
+import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
+
+/**
+ * Returns a wrapper that runs `action` when a wallet is connected, or opens the ConnectKit
+ * (Family) wallet-connect modal when it isn't. Used by entry points like the header's Swap /
+ * Bridge buttons so unauthenticated users go straight to connect instead of a modal's own
+ * connect step.
+ */
+export const useConnectGate = () => {
+ const { currentAccount } = useWeb3Context();
+ const { setOpen } = useModal();
+
+ return (action: () => void) => {
+ if (!currentAccount) {
+ setOpen(true);
+ return;
+ }
+ action();
+ };
+};
diff --git a/src/hooks/usePinnedMarket.ts b/src/hooks/usePinnedMarket.ts
new file mode 100644
index 0000000000..8309bfe3b2
--- /dev/null
+++ b/src/hooks/usePinnedMarket.ts
@@ -0,0 +1,23 @@
+import { useEffect } from 'react';
+import { useRootStore } from 'src/store/root';
+import { CustomMarket } from 'src/ui-config/marketsConfig';
+import { availableMarkets } from 'src/utils/marketsAndNetworksConfig';
+
+/**
+ * Pins the app's selected market to `market` for the lifetime of the calling page, restoring the
+ * user's prior market on unmount — so a page that must run on a single instance (e.g. staking /
+ * safety module on Core) can force it without a lasting global change. The header, lists, and tx
+ * modals all read the market from the store, so pinning here covers the whole page. No-op when
+ * already on `market`.
+ */
+export const usePinnedMarket = (market: CustomMarket): boolean => {
+ useEffect(() => {
+ const { currentMarket: prevMarket, setCurrentMarket } = useRootStore.getState();
+ if (prevMarket !== market) {
+ setCurrentMarket(market, true); // true = don't touch the URL query param
+ return () => setCurrentMarket(prevMarket, true);
+ }
+ }, [market]);
+
+ return availableMarkets.includes(market);
+};
diff --git a/src/hooks/useReserveActionState.tsx b/src/hooks/useReserveActionState.tsx
index 6a9db8200e..9ad2d12f88 100644
--- a/src/hooks/useReserveActionState.tsx
+++ b/src/hooks/useReserveActionState.tsx
@@ -1,8 +1,7 @@
import { ExternalLinkIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Button, Stack, SvgIcon, Typography } from '@mui/material';
+import { Alert, Button, Stack, SvgIcon, Typography } from '@mui/material';
import { Link, ROUTES } from 'src/components/primitives/Link';
-import { Warning } from 'src/components/primitives/Warning';
import { getEmodeMessage } from 'src/components/transactions/Emode/EmodeNaming';
import { isFunSupplyAsset } from 'src/components/transactions/FunCheckout/funSupplyAssets';
import {
@@ -71,11 +70,11 @@ export const useReserveActionState = ({
eModeBorrowDisabled ||
maxAmountToBorrow === '0',
alerts: (
-
+
{balance === '0' && !isGho && (
<>
{currentNetworkConfig.isTestnet ? (
-
+
Your {networkName} wallet is empty. Get free test {reserve.name} at
{' '}
@@ -108,13 +107,12 @@ export const useReserveActionState = ({
)}
-
+
) : (
)}
@@ -122,29 +120,29 @@ export const useReserveActionState = ({
)}
{(balance !== '0' || isGho) && user?.totalCollateralMarketReferenceCurrency === '0' && (
-
+ To borrow you need to supply any asset to be used as collateral.
-
+
)}
{isolationModeBorrowDisabled && (
-
+ Collateral usage is limited because of Isolation mode.
-
+
)}
{eModeBorrowDisabled && isolationModeBorrowDisabled && (
-
+
Borrowing is unavailable because you’ve enabled Efficiency Mode (E-Mode) and Isolation
mode. To manage E-Mode and Isolation mode visit your{' '}
Dashboard.
-
+
)}
{eModeBorrowDisabled && !isolationModeBorrowDisabled && (
-
+
Borrowing is unavailable because you’ve enabled Efficiency Mode (E-Mode) for{' '}
{replaceUnderscoresWithSpaces(
@@ -153,16 +151,16 @@ export const useReserveActionState = ({
category. To manage E-Mode categories visit your{' '}
Dashboard.
-
+
)}
{!eModeBorrowDisabled && isolationModeBorrowDisabled && (
-
+
Borrowing is unavailable because you’re using Isolation mode. To manage Isolation mode
visit your Dashboard.
-
+
)}
{maxAmountToSupply === '0' &&
diff --git a/src/layouts/AppFooter.tsx b/src/layouts/AppFooter.tsx
index 604995cdb9..83a4f32440 100644
--- a/src/layouts/AppFooter.tsx
+++ b/src/layouts/AppFooter.tsx
@@ -1,9 +1,13 @@
import { Trans } from '@lingui/macro';
-import { GitHub, Instagram, LinkedIn, X } from '@mui/icons-material';
-import { Box, styled, SvgIcon, Typography } from '@mui/material';
+import GitHub from '@mui/icons-material/GitHub';
+import Instagram from '@mui/icons-material/Instagram';
+import LinkedIn from '@mui/icons-material/LinkedIn';
+import X from '@mui/icons-material/X';
+import { Box, Container, styled, SvgIcon, Typography } from '@mui/material';
import { DuneIcon, TikTok } from 'public/icons/footer/icons';
import { Link } from 'src/components/primitives/Link';
import { useRootStore } from 'src/store/root';
+import { figVars } from 'src/utils/figmaColors';
import { useShallow } from 'zustand/shallow';
import DiscordIcon from '/public/icons/discord.svg';
@@ -13,14 +17,14 @@ interface StyledLinkProps {
onClick?: React.MouseEventHandler;
}
-const StyledLink = styled(Link)(({ theme }) => ({
- color: theme.palette.text.muted,
+const StyledLink = styled(Link)({
+ color: figVars['fg-3'],
'&:hover': {
- color: theme.palette.text.primary,
+ color: figVars['fg-1'],
},
display: 'flex',
alignItems: 'center',
-}));
+});
const FOOTER_ICONS = [
{
@@ -114,39 +118,46 @@ export function AppFooter() {
return (
({
- display: 'flex',
- padding: ['22px 0px 40px 0px', '0 22px 0 40px', '20px 22px'],
width: '100%',
- justifyContent: 'space-between',
- alignItems: 'center',
- gap: '22px',
- flexDirection: ['column', 'column', 'row'],
boxShadow:
theme.palette.mode === 'light'
? 'inset 0px 1px 0px rgba(0, 0, 0, 0.04)'
: 'inset 0px 1px 0px rgba(255, 255, 255, 0.12)',
})}
>
-
- {FOOTER_LINKS.map((link) => (
-
- {link.label}
-
- ))}
-
-
- {FOOTER_ICONS.map((icon) => (
-
-
- {icon.icon}
-
-
- ))}
-
+ {/* Horizontal padding + maxWidth come from the themed MuiContainer breakpoint ladder, same as
+ AppHeader, so the footer's content edges line up with the header's at every viewport width. */}
+
+
+ {FOOTER_LINKS.map((link) => (
+
+ {link.label}
+
+ ))}
+
+
+ {FOOTER_ICONS.map((icon) => (
+
+
+ {icon.icon}
+
+
+ ))}
+
+
);
}
diff --git a/src/layouts/AppGlobalStyles.tsx b/src/layouts/AppGlobalStyles.tsx
index ca31f629ea..b1f87bb6f2 100644
--- a/src/layouts/AppGlobalStyles.tsx
+++ b/src/layouts/AppGlobalStyles.tsx
@@ -1,61 +1,45 @@
-import { useMediaQuery } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';
-import { createTheme, ThemeProvider } from '@mui/material/styles';
-import { deepmerge } from '@mui/utils';
-import React, { ReactNode, useEffect, useMemo, useState } from 'react';
+import GlobalStyles from '@mui/material/GlobalStyles';
+import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles';
+import { ReactNode, useMemo } from 'react';
+import { FaviconSwitcher } from 'src/components/FaviconSwitcher';
-import { getDesignTokens, getThemedComponents } from '../utils/theme';
-
-export const ColorModeContext = React.createContext({
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- toggleColorMode: () => {},
-});
-
-type Mode = 'light' | 'dark';
+import { buildP3Overrides, createAppTheme } from '../utils/theme';
/**
- * Main Layout component which wrapps around the whole app
- * @param param0
- * @returns
+ * Main layout wrapper around the whole app. Provides the MUI theme via the CSS-variables
+ * engine: both color schemes are baked into CSS custom properties once, and light/dark is
+ * switched by toggling the `data-mui-color-scheme` attribute on (persisted by MUI,
+ * seeded from the OS preference). Components read/set the scheme via `useColorScheme()`.
*/
export function AppGlobalStyles({ children }: { children: ReactNode }) {
- const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
- const [mode, setMode] = useState(prefersDarkMode ? 'dark' : 'light');
- const colorMode = useMemo(
- () => ({
- toggleColorMode: () => {
- setMode((prevMode) => {
- const newMode = prevMode === 'light' ? 'dark' : 'light';
- localStorage.setItem('colorMode', newMode);
- return newMode;
- });
+ const theme = useMemo(() => createAppTheme(), []);
+
+ // Display-P3 layer: on wide-gamut displays that support the syntax, override the sRGB
+ // `--mui-palette-*` vars with their P3 equivalents. Everything else keeps the sRGB base.
+ const p3Styles = useMemo(() => {
+ const { light, dark } = buildP3Overrides(theme);
+ return {
+ '@supports (color: color(display-p3 1 1 1))': {
+ '@media (color-gamut: p3)': {
+ // Doubled selectors (specificity 0,2,0) beat MUI's own var sheets (0,1,0), so the
+ // P3 layer wins regardless of stylesheet source order — and still match both
+ // and the showcase's local `data-mui-color-scheme` wrapper.
+ ':root:root, [data-mui-color-scheme="light"][data-mui-color-scheme="light"]': light,
+ '[data-mui-color-scheme="dark"][data-mui-color-scheme="dark"]': dark,
+ },
},
- }),
- []
- );
-
- useEffect(() => {
- const initialMode = localStorage?.getItem('colorMode') as Mode;
- if (initialMode) {
- setMode(initialMode);
- } else if (prefersDarkMode) {
- setMode('dark');
- }
- }, []);
-
- const theme = useMemo(() => {
- const themeCreate = createTheme(getDesignTokens(mode));
- return deepmerge(themeCreate, getThemedComponents(themeCreate));
- }, [mode]);
+ };
+ }, [theme]);
return (
-
-
- {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
-
-
- {children}
-
-
+
+ {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
+
+
+
+
+ {children}
+
);
}
diff --git a/src/layouts/AppHeader.tsx b/src/layouts/AppHeader.tsx
index c798446032..52c63b5cfd 100644
--- a/src/layouts/AppHeader.tsx
+++ b/src/layouts/AppHeader.tsx
@@ -1,13 +1,13 @@
-import {
- InformationCircleIcon,
- SparklesIcon,
- SwitchHorizontalIcon,
-} from '@heroicons/react/outline';
+import { InformationCircleIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
import {
Badge,
Button,
CircularProgress,
+ Container,
+ ListItemText,
+ Menu,
+ MenuItem,
NoSsr,
Slide,
styled,
@@ -22,19 +22,33 @@ import * as React from 'react';
import { useEffect, useState } from 'react';
import { AvatarSize } from 'src/components/Avatar';
import { ContentWithTooltip } from 'src/components/ContentWithTooltip';
+import { AaveLogo, AaveLogoMark } from 'src/components/icons/AaveLogo';
+import { BridgeIcon } from 'src/components/icons/BridgeIcon';
+import { ChevronUpDownIcon } from 'src/components/icons/ChevronUpDownIcon';
+import { SwapIcon } from 'src/components/icons/SwapIcon';
import { AAVE_PRO_URL } from 'src/components/MarketSwitcher';
import { UserDisplay } from 'src/components/UserDisplay';
import { ConnectWalletButton } from 'src/components/WalletConnection/ConnectWalletButton';
+import { useConnectGate } from 'src/hooks/useConnectGate';
import { useModalContext } from 'src/hooks/useModal';
import { useSwapOrdersTracking } from 'src/hooks/useSwapOrdersTracking';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
+import { iconButtonSx } from 'src/utils/buttonStyles';
+import { figVars } from 'src/utils/figmaColors';
import { ENABLE_TESTNET, FORK_ENABLED, isFeatureEnabled } from 'src/utils/marketsAndNetworksConfig';
+import { motion } from 'src/utils/motion';
+import { darkScheme } from 'src/utils/theme';
import { useShallow } from 'zustand/shallow';
import { Link } from '../components/primitives/Link';
-import { uiConfig } from '../uiConfig';
import { NavItems } from './components/NavItems';
+import {
+ ENV_BADGE_ENABLED,
+ HEADER_COLLAPSE_BELOW,
+ HEADER_HEIGHT,
+ HEADER_MOBILE_BELOW,
+} from './headerLayout';
import { MobileMenu } from './MobileMenu';
import { SettingsMenu } from './SettingsMenu';
@@ -49,8 +63,8 @@ const StyledBadge = styled(Badge)(({ theme }) => ({
borderRadius: '20px',
width: '10px',
height: '10px',
- backgroundColor: `${theme.palette.secondary.main}`,
- color: `${theme.palette.secondary.main}`,
+ backgroundColor: `${theme.vars.palette.secondary.main}`,
+ color: `${theme.vars.palette.secondary.main}`,
'&::after': {
position: 'absolute',
top: 0,
@@ -75,13 +89,17 @@ const StyledBadge = styled(Badge)(({ theme }) => ({
},
}));
+const desktopOnlyBlock = { xs: 'none', [HEADER_MOBILE_BELOW]: 'block' } as const;
+const desktopOnlyInlineFlex = { xs: 'none', [HEADER_MOBILE_BELOW]: 'inline-flex' } as const;
+
function HideOnScroll({ children }: Props) {
const { breakpoints } = useTheme();
- const md = useMediaQuery(breakpoints.down('md'));
- const trigger = useScrollTrigger({ threshold: md ? 160 : 80 });
+ const mobile = useMediaQuery(breakpoints.down(HEADER_MOBILE_BELOW));
+ const trigger = useScrollTrigger({ threshold: 80 });
+ // Mobile keeps the header pinned (never hides on scroll); desktop still hides past the threshold.
return (
-
+
{children}
);
@@ -89,11 +107,36 @@ function HideOnScroll({ children }: Props) {
const SWITCH_VISITED_KEY = 'switchVisited';
+const testModeInk = {
+ color: '#00B3A6',
+ '@supports (color: color(display-p3 0 0 0))': {
+ color: 'color(display-p3 0.1686 0.6784 0.6431)',
+ },
+ transition: `color ${motion.duration.hover}ms ${motion.easing.standard}`,
+ '&:hover, &[aria-expanded="true"]': { color: figVars['green-1'] },
+ ...darkScheme({
+ color: '#00C1B8',
+ '&:hover, &[aria-expanded="true"]': { color: figVars['green-3'] },
+ }),
+};
+
+// Fork badge — intentionally off-brand magenta to stand out.
+const envBadgeSx = {
+ backgroundColor: '#B6509E',
+ boxShadow: 'none',
+ '&:hover, &.Mui-focusVisible': { backgroundColor: 'rgba(182, 80, 158, 0.7)', boxShadow: 'none' },
+ // The pill variant tints on hover via a ::before overlay; the badge steps its own fill instead.
+ '&:hover::before, &.Mui-focusVisible::before': { backgroundColor: 'transparent' },
+};
+
export function AppHeader() {
const { breakpoints } = useTheme();
- const md = useMediaQuery(breakpoints.down('md'));
- const sm = useMediaQuery(breakpoints.down('sm'));
- const smd = useMediaQuery('(max-width:1120px)');
+ const mobile = useMediaQuery(breakpoints.down(HEADER_MOBILE_BELOW));
+ const belowCollapse = useMediaQuery(breakpoints.down(HEADER_COLLAPSE_BELOW));
+ const collapsed = ENV_BADGE_ENABLED || belowCollapse;
+ const collapsingTriggerSx = collapsed
+ ? [iconButtonSx, { alignItems: 'center', '& .MuiButton-startIcon': { mx: 0 } }]
+ : { p: '0 0.88rem', minWidth: 'unset', alignItems: 'center' };
const [, setVisitedSwitch] = useState(() => {
if (typeof window === 'undefined') return true;
@@ -112,26 +155,18 @@ export function AppHeader() {
const { openSwitch, openBridge, openReadMode } = useModalContext();
const { readOnlyMode } = useWeb3Context();
- const [walletWidgetOpen, setWalletWidgetOpen] = useState(false);
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+ const openOrConnect = useConnectGate();
const { hasActiveOrders } = useSwapOrdersTracking();
useEffect(() => {
- if (mobileDrawerOpen && !md) {
+ if (!mobile) {
setMobileDrawerOpen(false);
}
- if (walletWidgetOpen) {
- setWalletWidgetOpen(false);
- }
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [md]);
+ }, [mobile]);
- const headerHeight = 48;
-
- const toggleMobileMenu = (state: boolean) => {
- if (md) setMobileDrawerOpen(state);
- setMobileMenuOpen(state);
- };
+ const [testModeAnchor, setTestModeAnchor] = useState(null);
+ const testModeOpen = Boolean(testModeAnchor);
const disableTestnet = () => {
localStorage.setItem('testnetsEnabled', 'false');
@@ -152,33 +187,13 @@ export function AppHeader() {
const handleSwitchClick = () => {
localStorage.setItem(SWITCH_VISITED_KEY, 'true');
setVisitedSwitch(true);
- openSwitch();
+ openOrConnect(openSwitch);
};
const handleBridgeClick = () => {
- openBridge();
+ openOrConnect(openBridge);
};
- const testnetTooltip = (
-
-
- Testnet mode is ON
-
-
- The app is running in testnet mode. Learn how it works in{' '}
-
- FAQ.
-
-
-
- Disable testnet
-
-
- );
-
const forkTooltip = (
@@ -187,7 +202,7 @@ export function AppHeader() {
The app is running in fork mode.
-
+ Disable fork
@@ -200,195 +215,217 @@ export function AppHeader() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
sx={(theme) => ({
- height: headerHeight,
+ height: HEADER_HEIGHT,
position: 'sticky',
top: 0,
transition: theme.transitions.create('top'),
zIndex: theme.zIndex.appBar,
- bgcolor: theme.palette.background.header,
- padding: {
- xs: mobileMenuOpen || walletWidgetOpen ? '8px 20px' : '8px 8px 8px 20px',
- xsm: '8px 20px',
- },
+ bgcolor: 'bg-3',
+ ...darkScheme({ backgroundColor: figVars['bg-1'] }),
display: 'flex',
- alignItems: 'center',
- flexDirection: 'space-between',
- boxShadow: 'inset 0px -1px 0px rgba(242, 243, 247, 0.16)',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ boxShadow: `inset 0px -1px 0px ${figVars['border-0']}`,
})}
>
- setMobileMenuOpen(false)}
>
-
-
-
+ setMobileDrawerOpen(false)}
+ >
+
+
+
+
+
+
+
{ENABLE_TESTNET && (
-
-
+
+ ) => setTestModeAnchor(e.currentTarget)}
+ onKeyDown={(e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ setTestModeAnchor(e.currentTarget);
+ }
+ }}
sx={{
- backgroundColor: '#B6509E',
- '&:hover, &.Mui-focusVisible': { backgroundColor: 'rgba(182, 80, 158, 0.7)' },
+ display: 'flex',
+ alignItems: 'center',
+ gap: 1,
+ minHeight: '28px',
+ cursor: 'pointer',
+ ...testModeInk,
}}
>
- TESTNET
-
-
-
-
-
+
+ Test Mode
+
+
+
+
+
)}
-
-
- {FORK_ENABLED && currentMarketData?.isFork && (
-
+
+ {FORK_ENABLED && currentMarketData?.isFork && (
+
+
+ FORK
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {!mobile && (
- FORK
-
-
-
+
+ {collapsed ? 'V4' : 'Aave V4'}
+
-
- )}
-
+ )}
+
-
-
-
-
-
-
-
-
-
- {smd ? 'V4' : 'Aave V4'}
-
-
-
+
+
+
+ {hasActiveOrders ? (
+ theme.vars.palette.grey[200],
+ }}
+ />
+ ) : (
+
+ )}
+
+ }
+ sx={collapsingTriggerSx}
+ aria-label="Switch tool"
+ disabled={!showSwitchButton}
+ >
+ {!collapsed && (
+
+ Swap
+
+ )}
+
+
+
-
-
-
+
- {!smd && (
-
- Bridge GHO
-
- )}
-
-
-
-
-
-
+ }
+ sx={collapsingTriggerSx}
+ >
+ {!collapsed && (
+
+ Bridge GHO
+
+ )}
+
+
+
-
-
+ {readOnlyMode ? (
{
+ openReadMode();
+ }}
>
- {!smd && (
-
- Swap
-
- )}
-
- {hasActiveOrders ? (
- theme.palette.grey[200],
- }}
- />
- ) : (
-
-
-
- )}
-
+
-
-
-
- {readOnlyMode ? (
- {
- openReadMode();
- }}
- >
-
-
- ) : (
-
- )}
+ ) : (
+
+ )}
-
-
-
+ {!mobile && }
- {!walletWidgetOpen && (
-
-
+
+
- )}
+
);
diff --git a/src/layouts/MobileMenu.tsx b/src/layouts/MobileMenu.tsx
index 711b0f0984..1d7c6869b3 100644
--- a/src/layouts/MobileMenu.tsx
+++ b/src/layouts/MobileMenu.tsx
@@ -1,27 +1,19 @@
-import { MenuIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import { useLingui } from '@lingui/react';
-import {
- Box,
- Button,
- Divider,
- List,
- ListItem,
- ListItemIcon,
- ListItemText,
- SvgIcon,
- Typography,
-} from '@mui/material';
-import React, { ReactNode, useEffect, useState } from 'react';
+import { Box, Button, Divider, List, ListItem, ListItemText } from '@mui/material';
+import { useEffect, useState } from 'react';
+import { BridgeIcon } from 'src/components/icons/BridgeIcon';
+import { SwapIcon } from 'src/components/icons/SwapIcon';
+import { AAVE_PRO_URL } from 'src/components/MarketSwitcher';
+import { Link } from 'src/components/primitives/Link';
+import { useConnectGate } from 'src/hooks/useConnectGate';
import { useModalContext } from 'src/hooks/useModal';
-import { PROD_ENV } from 'src/utils/marketsAndNetworksConfig';
+import { useRootStore } from 'src/store/root';
+import { figVars } from 'src/utils/figmaColors';
+import { isFeatureEnabled, PROD_ENV } from 'src/utils/marketsAndNetworksConfig';
-import { Link } from '../components/primitives/Link';
-import { moreNavigation } from '../ui-config/menu-items';
import { DarkModeSwitcher } from './components/DarkModeSwitcher';
import { DrawerWrapper } from './components/DrawerWrapper';
import { LanguageListItem, LanguagesList } from './components/LanguageSwitcher';
-import { MobileCloseButton } from './components/MobileCloseButton';
import { NavItems } from './components/NavItems';
import { ShieldSwitcher } from './components/ShieldSwitcher';
import { TestNetModeSwitcher } from './components/TestNetModeSwitcher';
@@ -29,100 +21,194 @@ import { TestNetModeSwitcher } from './components/TestNetModeSwitcher';
interface MobileMenuProps {
open: boolean;
setOpen: (value: boolean) => void;
- headerHeight: number;
}
-const MenuItemsWrapper = ({ children, title }: { children: ReactNode; title: ReactNode }) => (
-
-
-
- {title}
-
+// The options scroll area: full-width so its scrollbar sits on the right edge, with 0.75rem inner
+// padding for the content.
+const scrollAreaSx = {
+ flex: 1,
+ minHeight: 0,
+ overflowY: 'auto',
+ px: '0.75rem',
+ pb: '3rem',
+} as const;
- {children}
-
+// Rows inside the drawer lists: 3rem tall, H3 label text, gutters zeroed so they align with the
+// scroll area's 0.75rem inset. Applied via sx so the shared row components (SettingSwitchRow,
+// LanguagesList) don't need to know about it.
+const menuListSx = {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '0.5rem',
+ '& .MuiListItem-root': {
+ minHeight: '3rem',
+ borderRadius: '0.5rem',
+ px: 0,
+ cursor: 'pointer',
+ },
+ '& .MuiListItemText-primary': { fontSize: '1.125rem', fontWeight: 500, lineHeight: '120%' },
+};
+
+// The hamburger (three rounded lines, per the design SVG) that morphs into an X. Rendered inside
+// one fixed-size button (below), so toggling never resizes the button and shifts the header.
+// One bar of the hamburger; the three uses below add position + the open-state transform.
+const toggleBar = {
+ position: 'absolute' as const,
+ left: '4px',
+ width: '16px',
+ height: '2px',
+ borderRadius: '1px',
+ backgroundColor: 'currentColor',
+ transition: 'transform 0.2s ease, opacity 0.2s ease',
+};
-
+const MenuToggleIcon = ({ open }: { open: boolean }) => (
+
+
+
+
);
-export const MobileMenu = ({ open, setOpen, headerHeight }: MobileMenuProps) => {
- const { i18n } = useLingui();
+export const MobileMenu = ({ open, setOpen }: MobileMenuProps) => {
const [isLanguagesListOpen, setIsLanguagesListOpen] = useState(false);
- const { openReadMode } = useModalContext();
+ // Drives the top scrim: it only shows once the options actually scroll, so it never dims the
+ // first row at rest.
+ const [scrolled, setScrolled] = useState(false);
+ const { openReadMode, openSwitch, openBridge } = useModalContext();
+ const openOrConnect = useConnectGate();
+ const currentMarketData = useRootStore((store) => store.currentMarketData);
+ const showSwitchButton = isFeatureEnabled.switch(currentMarketData);
useEffect(() => setIsLanguagesListOpen(false), [open]);
+ // A fresh scroll area always starts at the top, so reset on open / view switch.
+ useEffect(() => setScrolled(false), [open, isLanguagesListOpen]);
const handleOpenReadMode = () => {
setOpen(false);
openReadMode();
};
+ const handleSwap = () => {
+ setOpen(false);
+ openOrConnect(openSwitch);
+ };
+
+ const handleBridge = () => {
+ setOpen(false);
+ openOrConnect(openBridge);
+ };
+
return (
<>
- {open ? (
-
- ) : (
- setOpen(true)}
- >
-
-
-
-
- )}
+ setOpen(!open)}
+ >
+
+
-
+
+ {/* Fade scrim over the top of the scroll area (mirrors the bottom scrim). Only shown once
+ scrolled, so it never dims the first row at rest. Inset from the top by the drawer's
+ padding (clean band under the header) and from the right so it never touches the scrollbar. */}
+
{!isLanguagesListOpen ? (
<>
- Menu}>
+ {/* Only the options scroll — the action buttons below stay pinned. */}
+ setScrolled(e.currentTarget.scrollTop > 0)}>
-
- Global settings}>
-
-
-
- {PROD_ENV && }
- setIsLanguagesListOpen(true)} />
-
-
- Links}>
-
-
-
- Watch wallet
-
-
-
+
+ {/* Watch Wallet sits above the global-settings rows, no divider between them. */}
+ setOpen(false)}
>
+ Aave V4
+
+
- Migrate to Aave V3
+ Watch Wallet
- {moreNavigation.map((item, index) => (
-
-
- {item.icon}
-
-
- {i18n._(item.title)}
-
- ))}
+
+
+ {PROD_ENV && }
+ setIsLanguagesListOpen(true)} />
-
+
+
+
+ {/* Fade scrim over the bottom of the scroll area, in place of a divider. */}
+
+
+ }
+ onClick={handleSwap}
+ disabled={!showSwitchButton}
+ >
+ Swap
+
+ }
+ onClick={handleBridge}
+ >
+ Bridge GHO
+
+
+
>
) : (
-
- setIsLanguagesListOpen(false)} />
-
+ setScrolled(e.currentTarget.scrollTop > 0)}>
+
+ setIsLanguagesListOpen(false)} />
+
+
)}
>
diff --git a/src/layouts/SettingsMenu.tsx b/src/layouts/SettingsMenu.tsx
index cd5a6a98f0..4b976b0a08 100644
--- a/src/layouts/SettingsMenu.tsx
+++ b/src/layouts/SettingsMenu.tsx
@@ -1,7 +1,7 @@
-import { CogIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Button, ListItemText, Menu, MenuItem, SvgIcon, Typography } from '@mui/material';
+import { Button, Divider, ListItemText, Menu, MenuItem } from '@mui/material';
import React, { useState } from 'react';
+import { SettingsIcon } from 'src/components/icons/SettingsIcon';
import { useModalContext } from 'src/hooks/useModal';
import { DEFAULT_LOCALE } from 'src/libs/LanguageProvider';
import { useRootStore } from 'src/store/root';
@@ -64,18 +64,16 @@ export function SettingsMenu() {
return (
<>
-
-
-
+
diff --git a/src/layouts/SupportModal.tsx b/src/layouts/SupportModal.tsx
index 21a8dfbdcf..92ac117735 100644
--- a/src/layouts/SupportModal.tsx
+++ b/src/layouts/SupportModal.tsx
@@ -203,20 +203,11 @@ export const SupportModal = () => {
) : (
-
+ Support
-
+
Let us know how we can help you. You may also consider joining our community
@@ -224,7 +215,7 @@ export const SupportModal = () => {
-
}
/>
diff --git a/src/modules/bridge/BridgeTransactionListItem.tsx b/src/modules/bridge/BridgeTransactionListItem.tsx
index e69d916089..dc35bdea13 100644
--- a/src/modules/bridge/BridgeTransactionListItem.tsx
+++ b/src/modules/bridge/BridgeTransactionListItem.tsx
@@ -111,21 +111,16 @@ export const BridgeTransactionListItem = ({
-
+
{/* */}
-
+
-
- {age}
-
+
+ {age}
+
{dayjs.unix(blockTimestamp).format('MMMM D YYYY h:mm A')}
-
+
{executionState === undefined ? (
) : (
)}
-
+
@@ -178,25 +173,20 @@ const BridgeTransactionMobileListItem = ({
- {age}
-
+ {age}
+
{dayjs.unix(blockTimestamp).format('MMMM D YYYY h:mm A')}
-
+
-
+
{/* */}
@@ -222,7 +212,7 @@ const BridgeTransactionMobileListItem = ({
pl: 1,
pr: 1,
}}
- variant="outlined"
+ variant="tertiary"
href={`https://ccip.chain.link/tx/${txHash}`}
target="_blank"
>
@@ -257,15 +247,13 @@ const BridgeTransactionMobileListItem = ({
};
const TxStatus = ({ state }: { state: MessageExecutionState }) => {
- const { palette } = useTheme();
-
switch (state) {
case MessageExecutionState.UNTOUCHED:
case MessageExecutionState.IN_PROGRESS:
return (
-
+
Processing
@@ -289,7 +277,7 @@ const TxStatus = ({ state }: { state: MessageExecutionState }) => {
-
+
Success
@@ -313,7 +301,7 @@ const TxStatus = ({ state }: { state: MessageExecutionState }) => {
-
+
Failed
diff --git a/src/modules/bridge/BridgeWrapper.tsx b/src/modules/bridge/BridgeWrapper.tsx
index 65eb374f7a..c4c1a5f67f 100644
--- a/src/modules/bridge/BridgeWrapper.tsx
+++ b/src/modules/bridge/BridgeWrapper.tsx
@@ -54,7 +54,7 @@ export function BridgeWrapper() {
You don't have any bridge transactions{' '}
-
+ Bridge GHO
@@ -88,13 +88,13 @@ export function BridgeWrapper() {
>
{!downToSm && (
-
+ Asset
-
+ Source
@@ -104,13 +104,13 @@ export function BridgeWrapper() {
-
+ Age
-
+ Status
diff --git a/src/modules/bridge/TransactionListItemLoader.tsx b/src/modules/bridge/TransactionListItemLoader.tsx
index b9612d6c14..6257c7f4f5 100644
--- a/src/modules/bridge/TransactionListItemLoader.tsx
+++ b/src/modules/bridge/TransactionListItemLoader.tsx
@@ -7,14 +7,14 @@ export const TransactionListItemLoader = () => {
return (
-
+
{/* */}
-
+
@@ -24,18 +24,18 @@ export const TransactionListItemLoader = () => {
-
+
-
+
-
+
diff --git a/src/modules/dashboard/DashboardContentNoData.tsx b/src/modules/dashboard/DashboardContentNoData.tsx
index c092a77250..973ca468d2 100644
--- a/src/modules/dashboard/DashboardContentNoData.tsx
+++ b/src/modules/dashboard/DashboardContentNoData.tsx
@@ -7,8 +7,14 @@ interface DashboardContentNoDataProps {
export const DashboardContentNoData = ({ text }: DashboardContentNoDataProps) => {
return (
-
- {text}
+
+ {text}
);
};
diff --git a/src/modules/dashboard/DashboardContentWrapper.tsx b/src/modules/dashboard/DashboardContentWrapper.tsx
index a931aadc6b..8dc01f4938 100644
--- a/src/modules/dashboard/DashboardContentWrapper.tsx
+++ b/src/modules/dashboard/DashboardContentWrapper.tsx
@@ -1,117 +1,47 @@
-import { ChainId } from '@aave/contract-helpers';
-import { Trans } from '@lingui/macro';
-import { Box, Button, useMediaQuery, useTheme } from '@mui/material';
-import { useRouter } from 'next/router';
-import { ROUTES } from 'src/components/primitives/Link';
-import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
-import { useRootStore } from 'src/store/root';
-import { AUTH } from 'src/utils/events';
+import { Box } from '@mui/material';
import { BorrowAssetsList } from './lists/BorrowAssetsList/BorrowAssetsList';
import { BorrowedPositionsList } from './lists/BorrowedPositionsList/BorrowedPositionsList';
import { SuppliedPositionsList } from './lists/SuppliedPositionsList/SuppliedPositionsList';
import { SupplyAssetsList } from './lists/SupplyAssetsList/SupplyAssetsList';
+const paperWidth = { xs: '100%', lg: 'calc(50% - 1rem)' };
+
interface DashboardContentWrapperProps {
isBorrow: boolean;
}
export const DashboardContentWrapper = ({ isBorrow }: DashboardContentWrapperProps) => {
- const { breakpoints } = useTheme();
- const { currentAccount } = useWeb3Context();
- const router = useRouter();
- const trackEvent = useRootStore((store) => store.trackEvent);
-
- const currentMarketData = useRootStore((store) => store.currentMarketData);
- const isDesktop = useMediaQuery(breakpoints.up('lg'));
- const paperWidth = isDesktop ? 'calc(50% - 8px)' : '100%';
-
- const downToLg = useMediaQuery(breakpoints.down('lg'));
-
return (
-
- {currentMarketData.chainId === ChainId.polygon && !currentMarketData.v3}
+
-
- {currentAccount && !isBorrow && downToLg && (
-
- {
- router.push(ROUTES.history);
- trackEvent(AUTH.VIEW_TX_HISTORY);
- }}
- component="a"
- variant="surface"
- size="small"
- >
- View Transactions
-
-
- )}
-
-
-
-
-
-
+
+
- display: { xs: !isBorrow ? 'none' : 'block', lg: 'block' },
- width: paperWidth,
- }}
- >
- {currentAccount && (
-
- {
- router.push(ROUTES.history);
- trackEvent(AUTH.VIEW_TX_HISTORY);
- }}
- component="a"
- variant="surface"
- size="small"
- >
- View Transactions
-
-
- )}
+
-
-
+ display: { xs: !isBorrow ? 'none' : 'block', lg: 'block' },
+ width: paperWidth,
+ }}
+ >
+
+
);
diff --git a/src/modules/dashboard/DashboardEModeButton.tsx b/src/modules/dashboard/DashboardEModeButton.tsx
index 57acebd969..f83c01b8f6 100644
--- a/src/modules/dashboard/DashboardEModeButton.tsx
+++ b/src/modules/dashboard/DashboardEModeButton.tsx
@@ -7,6 +7,7 @@ import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvide
import { useModalContext } from 'src/hooks/useModal';
import { useRootStore } from 'src/store/root';
import { DASHBOARD, GENERAL } from 'src/utils/events';
+import { figVars } from 'src/utils/figmaColors';
import { replaceUnderscoresWithSpaces } from 'src/utils/utils';
import LightningBoltGradient from '/public/lightningBoltGradient.svg';
@@ -53,7 +54,7 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
e.stopPropagation();
}}
>
-
+ E-Mode
@@ -65,53 +66,49 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
data-cy={`emode-open`}
size="small"
variant="outlined"
- sx={(theme) => ({
+ sx={{
ml: 1,
borderRadius: '4px',
- p: 0,
- '&:after': {
- content: "''",
- position: 'absolute',
- left: -1,
- right: -1,
- bottom: -1,
- top: -1,
- background: isEModeDisabled ? 'transparent' : theme.palette.gradients.aaveGradient,
- borderRadius: '4px',
- },
- })}
+ height: { xs: '2.25rem', xsm: '1.75rem' },
+ px: { xs: '0.88rem', xsm: '0.62rem' },
+ ...(isEModeDisabled
+ ? { backgroundColor: open ? figVars['bg-6'] : undefined }
+ : {
+ backgroundColor: figVars['purple-1'],
+ '&:after': {
+ content: "''",
+ position: 'absolute',
+ inset: '1px',
+ borderRadius: '3px',
+ backgroundColor: figVars['bg-3'],
+ },
+ }),
+ }}
>
({
+ sx={{
display: 'inline-flex',
alignItems: 'center',
position: 'relative',
zIndex: 1,
- bgcolor: isEModeDisabled
- ? open
- ? theme.palette.background.disabled
- : theme.palette.background.surface
- : theme.palette.background.paper,
- px: '4px',
- borderRadius: '4px',
- })}
+ }}
>
{isEModeDisabled ? : }
{isEModeDisabled ? (
-
+
) : (
-
+
)}
@@ -119,7 +116,7 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
@@ -142,17 +139,17 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
{!isEModeDisabled && (
-
+ Asset category ({
+ sx={{
p: 2,
mb: 3,
borderRadius: '6px',
- border: `1px solid ${theme.palette.divider}`,
- })}
+ border: `1px solid ${figVars['border-2']}`,
+ }}
>
-
+
@@ -192,14 +189,14 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
)}
-
+
E-Mode increases your LTV for a selected category of assets.{' '}
Learn more
@@ -209,7 +206,8 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
{isEModeDisabled ? (
{
trackEvent(GENERAL.OPEN_MODAL, {
type: 'Enable E-Mode',
@@ -228,7 +226,8 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt
{
trackEvent(GENERAL.OPEN_MODAL, {
modal: 'Switch E-Mode',
diff --git a/src/modules/dashboard/DashboardListTopPanel.tsx b/src/modules/dashboard/DashboardListTopPanel.tsx
index e65b69feca..71c36d8739 100644
--- a/src/modules/dashboard/DashboardListTopPanel.tsx
+++ b/src/modules/dashboard/DashboardListTopPanel.tsx
@@ -39,7 +39,7 @@ export const DashboardListTopPanel = ({
flexDirection: { xs: 'column-reverse', xsm: 'row' },
px: { xs: 4, xsm: 6 },
py: 2,
- pl: { xs: '18px', xsm: '27px' },
+ pl: { xs: '18px', xsm: '0.25rem' },
...sx,
}}
>
diff --git a/src/modules/dashboard/DashboardTopPanel.tsx b/src/modules/dashboard/DashboardTopPanel.tsx
index 50c46d7e50..b99c704e7a 100644
--- a/src/modules/dashboard/DashboardTopPanel.tsx
+++ b/src/modules/dashboard/DashboardTopPanel.tsx
@@ -7,10 +7,11 @@ import Link from 'next/link';
import * as React from 'react';
import { useState } from 'react';
import { NetAPYTooltip } from 'src/components/infoTooltips/NetAPYTooltip';
-import { getMarketInfoById } from 'src/components/MarketSwitcher';
+import { getMarketInfoById, MarketSwitcher } from 'src/components/MarketSwitcher';
+import { PageHeader } from 'src/components/PageHeader/PageHeader';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { ROUTES } from 'src/components/primitives/Link';
-import { PageTitle } from 'src/components/TopInfoPanel/PageTitle';
import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { ZERO_ADDRESS } from 'src/modules/governance/utils/formatProposal';
@@ -21,8 +22,6 @@ import { useShallow } from 'zustand/shallow';
import { HealthFactorNumber } from '../../components/HealthFactorNumber';
import { NoData } from '../../components/primitives/NoData';
-import { TopInfoPanel } from '../../components/TopInfoPanel/TopInfoPanel';
-import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
import { useAppDataContext } from '../../hooks/app-data-provider/useAppDataProvider';
import { useEnhancedUserYield } from '../../hooks/useEnhancedUserYield';
import { LiquidationRiskParametresInfoModal } from './LiquidationRiskParametresModal/LiquidationRiskParametresModal';
@@ -141,8 +140,7 @@ export const DashboardTopPanel = () => {
.dividedBy(user?.totalCollateralMarketReferenceCurrency || '1')
.toFixed();
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const noDataTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
+ const showHealthFactor = Boolean(currentAccount) && user?.healthFactor !== '-1';
return (
<>
@@ -150,7 +148,7 @@ export const DashboardTopPanel = () => {
{
)}
-
- Dashboard}
- withMarketSwitcher={true}
- bridge={currentNetworkConfig.bridge}
- />
+
{showMigrateButton && !downToSM && (
-
+
-
+ Migrate to v3
@@ -185,25 +180,23 @@ export const DashboardTopPanel = () => {
}
>
- Net worth} loading={loading} hideIcon>
+ Net worth} loading={loading}>
{currentAccount ? (
) : (
-
+
)}
-
+
-
+ Net APY {
eventParams: { tooltip: 'NET APY: Dashboard Banner' },
}}
/>
-
+
}
loading={loading}
- hideIcon
>
{currentAccount && user && Number(user.netWorthUSD) > 0 ? (
) : (
-
+
)}
-
+
- {currentAccount && user?.healthFactor !== '-1' && (
-
- Health factor
-
- }
- loading={loading}
- hideIcon
- >
+ {showHealthFactor && (
+ Health factor} loading={loading}>
{
trackEvent(DASHBOARD.VIEW_RISK_DETAILS);
setOpen(true);
}}
/>
-
+
)}
{currentAccount && (
- Available rewards} loading={loading} hideIcon>
+ Available rewards} loading={loading}>
{
openClaimRewards()}
sx={{ minWidth: 'unset', ml: { xs: 0, xsm: 2 } }}
@@ -283,9 +263,9 @@ export const DashboardTopPanel = () => {
Claim
-
+
)}
-
+
Learn more
@@ -81,11 +82,7 @@ export const LiquidationRiskParametresInfoModal = ({
}
topValue={
-
+
}
bottomText={
@@ -108,8 +105,8 @@ export const LiquidationRiskParametresInfoModal = ({
value={loanToValue}
percent
variant="main12"
- color="common.white"
- symbolsColor="common.white"
+ color={onAccent}
+ symbolsColor={onAccent}
/>
}
bottomText={
diff --git a/src/modules/dashboard/LiquidationRiskParametresModal/components/HFContent.tsx b/src/modules/dashboard/LiquidationRiskParametresModal/components/HFContent.tsx
index 5543ccf200..3f5adeb07a 100644
--- a/src/modules/dashboard/LiquidationRiskParametresModal/components/HFContent.tsx
+++ b/src/modules/dashboard/LiquidationRiskParametresModal/components/HFContent.tsx
@@ -45,7 +45,7 @@ export const HFContent = ({ healthFactor }: HFContentProps) => {
height: 0,
borderStyle: 'solid',
borderWidth: '6px 4px 0 4px',
- borderColor: `${theme.palette.primary.main} transparent transparent transparent`,
+ borderColor: `${theme.vars.palette.primary.main} transparent transparent transparent`,
content: "''",
position: 'absolute',
left: dotPosition > 75 ? 'auto' : '50%',
diff --git a/src/modules/dashboard/LiquidationRiskParametresModal/components/InfoWrapper.tsx b/src/modules/dashboard/LiquidationRiskParametresModal/components/InfoWrapper.tsx
index 9a24075c3a..27f7e48c1a 100644
--- a/src/modules/dashboard/LiquidationRiskParametresModal/components/InfoWrapper.tsx
+++ b/src/modules/dashboard/LiquidationRiskParametresModal/components/InfoWrapper.tsx
@@ -1,5 +1,6 @@
import { AlertColor, Box, Typography } from '@mui/material';
import { ReactNode } from 'react';
+import { figVars } from 'src/utils/figmaColors';
interface InfoWrapperProps {
topValue: ReactNode;
@@ -20,8 +21,8 @@ export const InfoWrapper = ({
}: InfoWrapperProps) => {
return (
({
- border: `1px solid ${theme.palette.divider}`,
+ sx={{
+ border: `1px solid ${figVars['border-2']}`,
mb: 6,
borderRadius: '6px',
px: 4,
@@ -30,14 +31,14 @@ export const InfoWrapper = ({
'&:last-of-type': {
mb: 0,
},
- })}
+ }}
>
{topTitle}
-
+
{topDescription}
@@ -59,7 +60,7 @@ export const InfoWrapper = ({
{children}
-
+
{bottomText}
diff --git a/src/modules/dashboard/LiquidationRiskParametresModal/components/LTVContent.tsx b/src/modules/dashboard/LiquidationRiskParametresModal/components/LTVContent.tsx
index 09a247c6a0..f0c8180a1d 100644
--- a/src/modules/dashboard/LiquidationRiskParametresModal/components/LTVContent.tsx
+++ b/src/modules/dashboard/LiquidationRiskParametresModal/components/LTVContent.tsx
@@ -3,6 +3,7 @@ import { Trans } from '@lingui/macro';
import { AlertColor, Box, Typography, useTheme } from '@mui/material';
import { BigNumber } from 'bignumber.js';
import React from 'react';
+import { figVars } from 'src/utils/figmaColors';
import { FormattedNumber } from '../../../../components/primitives/FormattedNumber';
@@ -19,7 +20,7 @@ export const LTVContent = ({
currentLiquidationThreshold,
color,
}: LTVContentProps) => {
- const { palette } = useTheme();
+ const { vars } = useTheme();
const LTVLineWidth = valueToBigNumber(loanToValue)
.multipliedBy(100)
@@ -120,7 +121,7 @@ export const LTVContent = ({
height: 0,
borderStyle: 'solid',
borderWidth: '6px 4px 0 4px',
- borderColor: `${theme.palette.primary.main} transparent transparent transparent`,
+ borderColor: `${theme.vars.palette.primary.main} transparent transparent transparent`,
content: "''",
position: 'absolute',
left: LTVLineWidth > 75 ? 'auto' : '50%',
@@ -146,7 +147,7 @@ export const LTVContent = ({
>
-
+ MAX
@@ -168,7 +169,7 @@ export const LTVContent = ({
width: '100%',
borderRadius: '1px',
position: 'relative',
- bgcolor: 'divider',
+ bgcolor: 'border-2',
}}
>
100 ? 100 : CurrentLTVLineWidth}%`,
maxWidth: '100%',
- background: `repeating-linear-gradient(-45deg, ${palette.divider}, ${palette.divider} 4px, ${palette[color].main} 4px, ${palette[color].main} 7px)`,
+ background: `repeating-linear-gradient(-45deg, ${figVars['border-2']}, ${figVars['border-2']} 4px, ${vars.palette[color].main} 4px, ${vars.palette[color].main} 7px)`,
}}
/>
)}
diff --git a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
index 0fcd634176..d11426a175 100644
--- a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
+++ b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
@@ -1,14 +1,14 @@
import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers';
import { USD_DECIMALS, valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Alert, Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import { Fragment, useState } from 'react';
import { AssetCategoryMultiSelect } from 'src/components/AssetCategoryMultiselect';
import { VariableAPYTooltip } from 'src/components/infoTooltips/VariableAPYTooltip';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
-import { Warning } from 'src/components/primitives/Warning';
import { AssetCapsProvider } from 'src/hooks/useAssetCaps';
import { useCoingeckoCategories } from 'src/hooks/useCoinGeckoCategories';
import { AssetCategory, isAssetInCategoryDynamic } from 'src/modules/markets/utils/assetCategories';
@@ -88,7 +88,7 @@ export const BorrowAssetsList = () => {
const currentMarket = currentMarketData.market;
const { user, reserves, marketReferencePriceInUsd, loading } = useAppDataContext();
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
@@ -176,7 +176,7 @@ export const BorrowAssetsList = () => {
const RenderHeader: React.FC = () => {
return (
- {head.map((col) => (
+ {head.map((col, index) => (
{
setSortDesc={setSortDesc}
sortKey={col.sortKey}
source={'Borrow Dashboard'}
+ noTruncate={index === head.length - 1}
>
{col.title}
))}
-
+
);
};
@@ -217,14 +218,14 @@ export const BorrowAssetsList = () => {
width: '100%',
alignItems: 'center',
justifyContent: 'space-between',
- mr: 2,
+ mr: '0.62rem',
}}
>
Assets to borrow
- {!downToXSM && !isListCollapsed && (
+ {!isListCollapsed && (
{
withTopMargin
noData={borrowDisabled}
subChildrenComponent={
- <>
- {downToXSM && (
+
+ {user?.healthFactor !== '-1' && Number(user?.healthFactor) <= 1.1 && (
+
+
+ Be careful - You are very close to liquidation. Consider depositing more collateral
+ or paying down some of your borrowed positions
+
+
+ )}
+
+ {!borrowDisabled && (
<>
-
-
-
+ {user?.isInIsolationMode && (
+
+ Borrowing power and assets are limited due to Isolation mode.
+
+ Learn More
+
+
+ )}
+ {user?.isInEmode && (
+
+
+ In E-Mode some assets are not borrowable. Exit E-Mode to get access to all
+ assets
+
+
+ )}
+ {user?.totalCollateralMarketReferenceCurrency === '0' && (
+
+ To borrow you need to supply any asset to be used as collateral.
+
+ )}
>
)}
-
- {user?.healthFactor !== '-1' && Number(user?.healthFactor) <= 1.1 && (
-
-
- Be careful - You are very close to liquidation. Consider depositing more
- collateral or paying down some of your borrowed positions
-
-
- )}
-
- {!borrowDisabled && (
- <>
- {user?.isInIsolationMode && (
-
- Borrowing power and assets are limited due to Isolation mode.
-
- Learn More
-
-
- )}
- {user?.isInEmode && (
-
-
- In E-Mode some assets are not borrowable. Exit E-Mode to get access to all
- assets
-
-
- )}
- {user?.totalCollateralMarketReferenceCurrency === '0' && (
-
- To borrow you need to supply any asset to be used as collateral.
-
- )}
- >
- )}
- {borrowDisabled && (
-
-
- We couldn't find any assets related to your search. Try again with a
- different category.
-
-
- )}
-
- >
+ {borrowDisabled && (
+
+
+ We couldn't find any assets related to your search. Try again with a different
+ category.
+
+
+ )}
+
}
>
<>
- {!downToXSM && !!borrowReserves.length && }
+ {!showCards && !!borrowReserves.length && }
{sortedReserves?.map((item) => (
- {downToXSM ? (
+ {showCards ? (
) : (
diff --git a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListItem.tsx b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListItem.tsx
index c2debbe689..270d3aacdf 100644
--- a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListItem.tsx
+++ b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListItem.tsx
@@ -95,8 +95,9 @@ export const BorrowAssetsListItem = ({
{
openBorrow(underlyingAsset, currentMarket, name, 'dashboard');
}}
@@ -104,7 +105,8 @@ export const BorrowAssetsListItem = ({
Borrow {
diff --git a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListMobileItem.tsx b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListMobileItem.tsx
index d6eeb337eb..f499b484ff 100644
--- a/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListMobileItem.tsx
+++ b/src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListMobileItem.tsx
@@ -78,7 +78,7 @@ export const BorrowAssetsListMobileItem = ({
incentives={vIncentivesData}
address={variableDebtTokenAddress}
symbol={symbol}
- variant="secondary14"
+ variant="h5"
market={currentMarket}
protocolAction={ProtocolAction.borrow}
/>
@@ -86,7 +86,7 @@ export const BorrowAssetsListMobileItem = ({
openBorrow(underlyingAsset, currentMarket, name, 'dashboard')}
sx={{ mr: 1.5 }}
fullWidth
@@ -94,7 +94,7 @@ export const BorrowAssetsListMobileItem = ({
Borrow {
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const showEModeButton = currentMarketData.v3 && Object.keys(eModes).length > 1;
const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -118,7 +119,7 @@ export const BorrowedPositionsList = () => {
const RenderHeader: React.FC = () => {
return (
- {head.map((col) => (
+ {head.map((col, index) => (
{
setSortDesc={setSortDesc}
sortKey={col.sortKey}
source="Borrowed Positions Dashboard"
+ noTruncate={index === head.length - 1}
>
{col.title}
))}
-
+
);
};
@@ -196,7 +198,7 @@ export const BorrowedPositionsList = () => {
>
{sortedReserves.length ? (
<>
- {!downToXSM && }
+ {!showCards && }
{sortedReserves.map((item) => (
[state.currentMarket, state.currentMarketData])
);
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const { openBorrow, openRepay, openDebtSwitch } = useModalContext();
const { user } = useAppDataContext();
@@ -84,7 +85,7 @@ export const BorrowedPositionsListItem = ({
},
};
- if (downToXSM) {
+ if (showCards) {
return ;
} else {
return ;
@@ -168,6 +169,7 @@ const BorrowedPositionsListItemDesktop = ({
{showSwitchButton ? (
Swap
) : (
-
+ Borrow
)}
-
+ Repay
@@ -236,7 +238,7 @@ const BorrowedPositionsListItemMobile = ({
incentives={incentives}
address={variableDebtTokenAddress}
symbol={symbol}
- variant="secondary14"
+ variant="h5"
market={currentMarket}
protocolAction={ProtocolAction.borrow}
/>
@@ -260,7 +262,7 @@ const BorrowedPositionsListItemMobile = ({
)}
{
return (
-
+
{tooltip}
-
+
{children}
);
@@ -60,6 +61,7 @@ export const ListGhoAPRColumn = ({
{
return (
-
+
Since this is a test network, you can get any of the assets if you have ETH on your wallet
-
+
Faucet
.
diff --git a/src/modules/dashboard/lists/ListButtonsColumn.tsx b/src/modules/dashboard/lists/ListButtonsColumn.tsx
index eb95a55e2e..d4c08bd5b5 100644
--- a/src/modules/dashboard/lists/ListButtonsColumn.tsx
+++ b/src/modules/dashboard/lists/ListButtonsColumn.tsx
@@ -2,12 +2,7 @@ import { Box } from '@mui/material';
import { ReactNode } from 'react';
import { DASHBOARD_LIST_COLUMN_WIDTHS } from 'src/utils/dashboardSortUtils';
-interface ListButtonsColumnProps {
- children?: ReactNode;
- isColumnHeader?: boolean;
-}
-
-export const ListButtonsColumn = ({ children, isColumnHeader = false }: ListButtonsColumnProps) => {
+export const ListButtonsColumn = ({ children }: { children?: ReactNode }) => {
return (
diff --git a/src/modules/dashboard/lists/ListHeader.tsx b/src/modules/dashboard/lists/ListHeader.tsx
index 0c59648912..56e2e9ecf3 100644
--- a/src/modules/dashboard/lists/ListHeader.tsx
+++ b/src/modules/dashboard/lists/ListHeader.tsx
@@ -14,7 +14,7 @@ export const ListHeader = ({ head }: ListHeaderProps) => {
{head.map((title, i) => (
- {title}
+ {title}
))}
diff --git a/src/modules/dashboard/lists/ListItemCanBeCollateral.tsx b/src/modules/dashboard/lists/ListItemCanBeCollateral.tsx
index 4135c323ed..baa2e14d83 100644
--- a/src/modules/dashboard/lists/ListItemCanBeCollateral.tsx
+++ b/src/modules/dashboard/lists/ListItemCanBeCollateral.tsx
@@ -24,7 +24,7 @@ export const ListItemCanBeCollateral = ({
// NOTE: handled in ListItemIsolationBadge
return null;
} else {
- return ;
+ return ;
}
};
diff --git a/src/modules/dashboard/lists/ListItemLoader.tsx b/src/modules/dashboard/lists/ListItemLoader.tsx
index c8977c10bd..2eaa5d9e74 100644
--- a/src/modules/dashboard/lists/ListItemLoader.tsx
+++ b/src/modules/dashboard/lists/ListItemLoader.tsx
@@ -9,7 +9,7 @@ export const ListItemLoader = ({ columns }: { columns: number }) => {
-
+
@@ -22,7 +22,7 @@ export const ListItemLoader = ({ columns }: { columns: number }) => {
-
+
);
diff --git a/src/modules/dashboard/lists/ListItemWrapper.tsx b/src/modules/dashboard/lists/ListItemWrapper.tsx
index 6055950798..530cf7bf41 100644
--- a/src/modules/dashboard/lists/ListItemWrapper.tsx
+++ b/src/modules/dashboard/lists/ListItemWrapper.tsx
@@ -85,7 +85,11 @@ export const ListItemWrapper = ({
noWrap
sx={{ display: 'inline-flex', alignItems: 'center' }}
>
-
+
{symbol}
diff --git a/src/modules/dashboard/lists/ListLoader.tsx b/src/modules/dashboard/lists/ListLoader.tsx
index a382c099d1..c92fbf712d 100644
--- a/src/modules/dashboard/lists/ListLoader.tsx
+++ b/src/modules/dashboard/lists/ListLoader.tsx
@@ -1,5 +1,6 @@
import { Typography, useMediaQuery, useTheme } from '@mui/material';
import { ReactNode } from 'react';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListWrapper } from '../../../components/lists/ListWrapper';
import { ListHeader } from './ListHeader';
@@ -14,7 +15,7 @@ interface ListLoaderProps {
export const ListLoader = ({ title, withTopMargin, head }: ListLoaderProps) => {
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
return (
{
withTopMargin={withTopMargin}
>
<>
- {!downToXSM && }
- {!downToXSM ? (
+ {!showCards && }
+ {!showCards ? (
<>
diff --git a/src/modules/dashboard/lists/ListTopInfoItem.tsx b/src/modules/dashboard/lists/ListTopInfoItem.tsx
index eaf7149450..dd3af9dd7f 100644
--- a/src/modules/dashboard/lists/ListTopInfoItem.tsx
+++ b/src/modules/dashboard/lists/ListTopInfoItem.tsx
@@ -1,5 +1,6 @@
-import { Paper, Typography } from '@mui/material';
+import { Box } from '@mui/material';
import { ReactNode } from 'react';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from '../../../components/primitives/FormattedNumber';
@@ -12,23 +13,19 @@ interface ListTopInfoItemProps {
export const ListTopInfoItem = ({ title, value, percent, tooltip }: ListTopInfoItemProps) => {
return (
-
+ {title}
+ {tooltip}
+
+ ) : (
+ title
+ )
+ }
>
-
- {title}
-
-
-
- {tooltip}
-
+
+
);
};
diff --git a/src/modules/dashboard/lists/ListValueColumn.tsx b/src/modules/dashboard/lists/ListValueColumn.tsx
index beb7a10db2..1368704056 100644
--- a/src/modules/dashboard/lists/ListValueColumn.tsx
+++ b/src/modules/dashboard/lists/ListValueColumn.tsx
@@ -1,5 +1,6 @@
import { Box, Tooltip } from '@mui/material';
import { ReactNode } from 'react';
+import { onAccent } from 'src/utils/figmaColors';
import { ListColumn, ListColumnProps } from '../../../components/lists/ListColumn';
import { FormattedNumber } from '../../../components/primitives/FormattedNumber';
@@ -26,21 +27,16 @@ const Content = ({
{capsComponent}
{!withTooltip && !!subValue && !disabled && (
-
+
)}
>
);
@@ -71,16 +67,16 @@ export const ListValueColumn = ({
diff --git a/src/modules/dashboard/lists/ListValueRow.tsx b/src/modules/dashboard/lists/ListValueRow.tsx
index 943493d4db..1eb635db75 100644
--- a/src/modules/dashboard/lists/ListValueRow.tsx
+++ b/src/modules/dashboard/lists/ListValueRow.tsx
@@ -23,22 +23,12 @@ export const ListValueRow = ({
-
+
{capsComponent}
{!disabled && (
-
+
)}
diff --git a/src/modules/dashboard/lists/SlippageList.tsx b/src/modules/dashboard/lists/SlippageList.tsx
index 320bca36b4..64866f9acc 100644
--- a/src/modules/dashboard/lists/SlippageList.tsx
+++ b/src/modules/dashboard/lists/SlippageList.tsx
@@ -54,10 +54,10 @@ export const ListSlippageButton = ({
text={
-
+
Slippage tolerance{' '}
-
+
{selectedSlippage}%{' '}
@@ -66,7 +66,7 @@ export const ListSlippageButton = ({
}
- variant="secondary14"
+ variant="h5"
/>
}
disabled={false}
@@ -84,7 +84,7 @@ export const ListSlippageButton = ({
data-cy={`slippageMenu_${selectedSlippage}`}
>
-
+ Select slippage tolerance
@@ -116,8 +116,8 @@ export const ListSlippageButton = ({
Powered by
@@ -133,7 +133,7 @@ export const ListSlippageButton = ({
-
+
Velora
diff --git a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsList.tsx b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsList.tsx
index ff8ef5deab..fb40ac4dec 100644
--- a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsList.tsx
+++ b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsList.tsx
@@ -2,6 +2,7 @@ import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers';
import { Trans } from '@lingui/macro';
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import { Fragment, useMemo, useState } from 'react';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
@@ -67,7 +68,7 @@ export const SuppliedPositionsList = () => {
const currentNetworkConfig = useRootStore((store) => store.currentNetworkConfig);
const currentMarketData = useRootStore((store) => store.currentMarketData);
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -140,7 +141,7 @@ export const SuppliedPositionsList = () => {
const RenderHeader: React.FC = () => {
return (
- {head.map((col) => (
+ {head.map((col, index) => (
{
setSortDesc={setSortDesc}
sortKey={col.sortKey}
source="Supplied Positions Dashboard"
+ noTruncate={index === head.length - 1}
>
{col.title}
))}
-
+
);
};
@@ -234,11 +236,11 @@ export const SuppliedPositionsList = () => {
>
{sortedReserves.length ? (
<>
- {!downToXSM && }
+ {!showCards && }
{sortedReserves.map((item) => (
- {downToXSM ? (
+ {showCards ? (
) : (
diff --git a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx
index bcb6968fb5..e147574a29 100644
--- a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx
+++ b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx
@@ -111,6 +111,7 @@ export const SuppliedPositionsListItem = ({
{showSwitchButton ? (
{
@@ -130,6 +131,7 @@ export const SuppliedPositionsListItem = ({
) : (
openSupply(underlyingAsset, currentMarket, reserve.name, 'dashboard')}
@@ -138,8 +140,9 @@ export const SuppliedPositionsListItem = ({
)}
{
openWithdraw(underlyingAsset, currentMarket, reserve.name, 'dashboard');
}}
diff --git a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx
index ccf8856a20..c05c0a8291 100644
--- a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx
+++ b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx
@@ -95,7 +95,7 @@ export const SuppliedPositionsListMobileItem = ({
incentives={aIncentivesData}
address={aTokenAddress}
symbol={symbol}
- variant="secondary14"
+ variant="h5"
market={currentMarket}
protocolAction={ProtocolAction.supply}
/>
@@ -146,7 +146,7 @@ export const SuppliedPositionsListMobileItem = ({
)}
openWithdraw(underlyingAsset, currentMarket, reserve.name, 'dashboard')}
sx={{ ml: 1.5 }}
fullWidth
diff --git a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx
index 12864d5841..f0d15b7c81 100644
--- a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx
+++ b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx
@@ -1,14 +1,14 @@
import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers';
import { USD_DECIMALS, valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Alert, Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import { BigNumber } from 'bignumber.js';
import { Fragment, useState } from 'react';
import { AssetCategoryMultiSelect } from 'src/components/AssetCategoryMultiselect';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
-import { Warning } from 'src/components/primitives/Warning';
import { isFunSupplyAsset } from 'src/components/transactions/FunCheckout/funSupplyAssets';
import { AssetCapsProvider } from 'src/hooks/useAssetCaps';
import { useCoingeckoCategories } from 'src/hooks/useCoinGeckoCategories';
@@ -41,7 +41,7 @@ import { WalletEmptyInfo } from './WalletEmptyInfo';
const head = [
{ title: Assets, sortKey: 'symbol' },
- { title: Wallet balance, sortKey: 'walletBalance' },
+ { title: Balance, sortKey: 'walletBalance' },
{ title: APY, sortKey: 'supplyAPY' },
{
title: Can be collateral,
@@ -66,7 +66,7 @@ export const SupplyAssetsList = () => {
const wrappedTokenReserves = useWrappedTokens();
const { walletBalances, loading } = useWalletBalances(currentMarketData);
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
@@ -248,7 +248,7 @@ export const SupplyAssetsList = () => {
const RenderHeader: React.FC = () => {
return (
- {head.map((col) => (
+ {head.map((col, index) => (
{
setSortDesc={setSortDesc}
sortKey={col.sortKey}
source="Supplies Dashbaord"
+ noTruncate={index === head.length - 1}
>
{col.title}
))}
-
+
);
};
@@ -292,14 +293,14 @@ export const SupplyAssetsList = () => {
width: '100%',
alignItems: 'center',
justifyContent: 'space-between',
- mr: 2,
+ mr: '0.62rem',
}}
>
Assets to supply
- {!downToXSM && !isListCollapsed && (
+ {!isListCollapsed && (
{
noData={supplyDisabled}
subChildrenComponent={
<>
- {downToXSM && !isListCollapsed && (
-
-
-
- )}
-
+
{user?.isInIsolationMode ? (
-
+
Collateral usage is limited because of isolation mode.{' '}
Learn More
-
+
) : (
filteredSupplyReserves.length === 0 &&
!supplyDisabled &&
(isTestnet ? (
-
+ Your {networkName} wallet is empty. Get free test assets at {' '}
-
+
{networkName} Faucet
-
+
) : (
))
)}
{supplyDisabled && (
-
+
We couldn't find any assets related to your search. Try again with a
different category.
-
+
)}
@@ -379,7 +367,7 @@ export const SupplyAssetsList = () => {
}
>
<>
- {!downToXSM && !!sortedReserves && !supplyDisabled && }
+ {!showCards && !!sortedReserves && !supplyDisabled && }
{sortedReserves.map((item) => (
diff --git a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsListItem.tsx b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsListItem.tsx
index 4f7023053a..e81213198e 100644
--- a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsListItem.tsx
+++ b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsListItem.tsx
@@ -1,6 +1,5 @@
import { ProtocolAction } from '@aave/contract-helpers';
-import { SwitchHorizontalIcon } from '@heroicons/react/outline';
-import { EyeIcon } from '@heroicons/react/solid';
+import { InformationCircleIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
import {
Box,
@@ -15,8 +14,11 @@ import {
} from '@mui/material';
import { useState } from 'react';
import { ContentWithTooltip } from 'src/components/ContentWithTooltip';
+import { DotsHorizontalIcon } from 'src/components/icons/DotsHorizontalIcon';
+import { SwapIcon } from 'src/components/icons/SwapIcon';
import { IncentivesCard } from 'src/components/incentives/IncentivesCard';
import { WrappedTokenTooltipContent } from 'src/components/infoTooltips/WrappedTokenToolTipContent';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { NoData } from 'src/components/primitives/NoData';
import { Row } from 'src/components/primitives/Row';
@@ -28,8 +30,10 @@ import { useAssetCaps } from 'src/hooks/useAssetCaps';
import { useModalContext } from 'src/hooks/useModal';
import { useWrappedTokens } from 'src/hooks/useWrappedTokens';
import { useRootStore } from 'src/store/root';
+import { iconButtonSx } from 'src/utils/buttonStyles';
import { DashboardReserve } from 'src/utils/dashboardSortUtils';
import { DASHBOARD } from 'src/utils/events';
+import { onAccent } from 'src/utils/figmaColors';
import { isFeatureEnabled } from 'src/utils/marketsAndNetworksConfig';
import { showExternalIncentivesTooltip } from 'src/utils/utils';
@@ -49,7 +53,7 @@ export const SupplyAssetsListItem = (
params: DashboardReserve & { walletBalances: WalletBalancesMap }
) => {
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const { supplyCap } = useAssetCaps();
const wrappedTokenReserves = useWrappedTokens();
const currentMarket = useRootStore((store) => store.currentMarket);
@@ -82,7 +86,7 @@ export const SupplyAssetsListItem = (
walletBalancesMap: params.walletBalances,
};
- if (downToXSM) {
+ if (showCards) {
return ;
} else {
return ;
@@ -188,12 +192,7 @@ export const SupplyAssetsListItemDesktop = ({
justifyContent: 'center',
}}
>
-
+
@@ -237,7 +236,7 @@ export const SupplyAssetsListItemDesktop = ({
{debtCeiling.isMaxed ? (
-
+
) : (
- ...
+ }
+ >
+
+ Click me
+
+
+
+
+
+
+ Explanation of the metric goes here.
+
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/Section/index.tsx b/src/modules/dev/ComponentShowcase/components/Section/index.tsx
new file mode 100644
index 0000000000..b5f7333f72
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/Section/index.tsx
@@ -0,0 +1,37 @@
+import { Box, Typography } from '@mui/material';
+import { ReactNode } from 'react';
+import { figVars } from 'src/utils/figmaColors';
+
+interface SectionProps {
+ title: string;
+ description?: string;
+ children: ReactNode;
+}
+
+export const Section = ({ title, description, children }: SectionProps) => (
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {children}
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/ShowcaseLayout/index.tsx b/src/modules/dev/ComponentShowcase/components/ShowcaseLayout/index.tsx
new file mode 100644
index 0000000000..49fc92ffed
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/ShowcaseLayout/index.tsx
@@ -0,0 +1,186 @@
+import { MenuIcon } from '@heroicons/react/outline';
+import {
+ Box,
+ Container,
+ Drawer,
+ IconButton,
+ PaletteMode,
+ SvgIcon,
+ Typography,
+} from '@mui/material';
+import { useColorScheme } from '@mui/material/styles';
+import { ReactNode, useState } from 'react';
+import { Link } from 'src/components/primitives/Link';
+
+import { SHOWCASE_GROUPS, SHOWCASE_SECTIONS } from '../../utils/registry';
+import { ThemeControl } from '../ThemeControl';
+
+interface ShowcaseLayoutProps {
+ activeSlug: string;
+ children: ReactNode;
+}
+
+const SIDEBAR_WIDTH = 248;
+
+export const ShowcaseLayout = ({ activeSlug, children }: ShowcaseLayoutProps) => {
+ const { mode: appMode, systemMode } = useColorScheme();
+
+ // The showcase runs on its OWN color scheme (seeded once from the app's) so switching it
+ // here re-declares the CSS variables for this subtree only — via the `data-mui-color-scheme`
+ // attribute — without flipping the whole app. Colors below use `sx` palette shortcuts,
+ // which resolve to CSS-var refs and therefore follow that attribute.
+ const [scheme, setScheme] = useState(
+ () => (appMode === 'system' ? systemMode : appMode) ?? 'light'
+ );
+ const [mobileNavOpen, setMobileNavOpen] = useState(false);
+
+ // Some sections (page-wide banners) opt out of the max-width content container.
+ const fullBleed = SHOWCASE_SECTIONS.find((s) => s.slug === activeSlug)?.fullBleed ?? false;
+
+ // One nav block, reused by the desktop sidebar and the mobile drawer.
+ const nav = (
+ <>
+
+ Components
+
+
+ {SHOWCASE_GROUPS.map((group, index) => (
+
+
+ {group.label}
+
+
+ {group.sections.map((section) => {
+ const active = section.slug === activeSlug;
+ return (
+ setMobileNavOpen(false)}
+ sx={{
+ display: 'block',
+ py: 1,
+ px: 2,
+ borderRadius: '8px',
+ color: active ? 'fg-1' : 'fg-2',
+ backgroundColor: active ? 'selected' : 'transparent',
+ '&:hover': {
+ color: 'fg-1',
+ backgroundColor: active ? 'selected' : 'button-hover',
+ },
+ }}
+ >
+ {section.label}
+
+ );
+ })}
+
+ ))}
+ >
+ );
+
+ return (
+
+ {/* Persistent sidebar (md and up) */}
+
+ {nav}
+
+
+ {/* Mobile drawer (below md). It portals to , outside the local-scheme wrapper above,
+ so the inner Box re-declares `data-mui-color-scheme` to keep it on the showcase theme. */}
+ setMobileNavOpen(false)}
+ sx={{ display: { xs: 'block', md: 'none' } }}
+ PaperProps={{ sx: { width: SIDEBAR_WIDTH, border: 'none' } }}
+ >
+
+ {nav}
+
+
+
+ {/* Content */}
+
+
+
+ setMobileNavOpen(true)}
+ sx={{ display: { xs: 'inline-flex', md: 'none' }, ml: -1 }}
+ >
+
+
+
+
+
+ Component showcase
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+};
diff --git a/src/modules/dev/ComponentShowcase/components/Specimen/index.tsx b/src/modules/dev/ComponentShowcase/components/Specimen/index.tsx
new file mode 100644
index 0000000000..164adce784
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/Specimen/index.tsx
@@ -0,0 +1,55 @@
+import { Box, Typography } from '@mui/material';
+import { ReactNode } from 'react';
+import { figVars } from 'src/utils/figmaColors';
+
+interface SpecimenProps {
+ label?: string;
+ fullWidth?: boolean;
+ // Cross-axis alignment of the controls on the stage row. Defaults to 'center' (best for
+ // toggles); pass 'flex-start' when items differ in height (e.g. a field with error text) so
+ // their top edges line up.
+ align?: 'center' | 'flex-start';
+ children: ReactNode;
+}
+
+// A single example: a small uppercase caption above the component, which sits on a
+// plain bordered "stage" (no fill) — matching the reference showcase.
+export const Specimen = ({ label, fullWidth, align = 'center', children }: SpecimenProps) => (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {children}
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/SurfacesSection/index.tsx b/src/modules/dev/ComponentShowcase/components/SurfacesSection/index.tsx
new file mode 100644
index 0000000000..413d9db8bd
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/SurfacesSection/index.tsx
@@ -0,0 +1,102 @@
+import { Box, Button, Paper, Typography } from '@mui/material';
+import { ListWrapper } from 'src/components/lists/ListWrapper';
+import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
+import { Row } from 'src/components/primitives/Row';
+import { ReserveOverviewBox } from 'src/components/ReserveOverviewBox';
+import { TopInfoPanel } from 'src/components/TopInfoPanel/TopInfoPanel';
+import { TopInfoPanelItem } from 'src/components/TopInfoPanel/TopInfoPanelItem';
+import { StakeActionBox } from 'src/modules/staking/StakeActionBox';
+
+import { Section } from '../Section';
+import { Specimen } from '../Specimen';
+
+const PAPER_VARIANTS = ['elevation', 'outlined', 'modal', 'card'] as const;
+
+export const SurfacesSection = () => (
+
+ {PAPER_VARIANTS.map((variant) => (
+
+
+ Paper {variant}
+
+
+ ))}
+
+
+
+ Card title}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Showcase panel
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ —}
+ dataCy="showcaseStake"
+ >
+
+ Stake
+
+
+
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/Swatch/index.tsx b/src/modules/dev/ComponentShowcase/components/Swatch/index.tsx
new file mode 100644
index 0000000000..e89bbcb2a8
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/Swatch/index.tsx
@@ -0,0 +1,35 @@
+import { Box } from '@mui/material';
+import { FigmaColorName, figVars } from 'src/utils/figmaColors';
+
+import { TokenHexLabel } from '../TokenHexLabel';
+
+interface SwatchProps {
+ name: FigmaColorName;
+ value: string;
+}
+
+// A checkerboard backing so alpha tokens (borders/shadows/scrim) stay visible.
+const CHECKERBOARD = {
+ backgroundImage:
+ 'linear-gradient(45deg, #c4c4c4 25%, transparent 25%), linear-gradient(-45deg, #c4c4c4 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #c4c4c4 75%), linear-gradient(-45deg, transparent 75%, #c4c4c4 75%)',
+ backgroundSize: '12px 12px',
+ backgroundPosition: '0 0, 0 6px, 6px -6px, -6px 0px',
+};
+
+export const Swatch = ({ name, value }: SwatchProps) => (
+
+
+
+
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/ThemeControl/index.tsx b/src/modules/dev/ComponentShowcase/components/ThemeControl/index.tsx
new file mode 100644
index 0000000000..b127d68324
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/ThemeControl/index.tsx
@@ -0,0 +1,30 @@
+import { Box, Button, PaletteMode, Typography } from '@mui/material';
+
+interface ThemeControlProps {
+ mode: PaletteMode;
+ onChange: (mode: PaletteMode) => void;
+}
+
+// Segmented Light/Dark control for the showcase's local theme. Uses the themed
+// Button variants so it reads natively in whichever mode is active.
+export const ThemeControl = ({ mode, onChange }: ThemeControlProps) => (
+
+
+ Theme
+
+
+ {(['light', 'dark'] as const).map((value) => (
+ onChange(value)}
+ sx={{ textTransform: 'capitalize' }}
+ >
+ {value}
+
+ ))}
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/TogglesBadgesSection/index.tsx b/src/modules/dev/ComponentShowcase/components/TogglesBadgesSection/index.tsx
new file mode 100644
index 0000000000..ad56a5de20
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/TogglesBadgesSection/index.tsx
@@ -0,0 +1,73 @@
+import { Box, Typography } from '@mui/material';
+import { useState } from 'react';
+import { BadgeSize, ExclamationBadge } from 'src/components/badges/ExclamationBadge';
+import { StyledTxModalToggleButton } from 'src/components/StyledToggleButton';
+import { StyledTxModalToggleGroup } from 'src/components/StyledToggleButtonGroup';
+
+import { Section } from '../Section';
+import { Specimen } from '../Specimen';
+
+interface ToggleOption {
+ value: string;
+ label: string;
+ disabled?: boolean;
+}
+
+const ToggleDemo = ({ options, initial }: { options: ToggleOption[]; initial: string }) => {
+ const [value, setValue] = useState(initial);
+ return (
+ v && setValue(v)}>
+ {options.map((o) => (
+
+ {o.label}
+
+ ))}
+
+ );
+};
+
+export const TogglesBadgesSection = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/src/modules/dev/ComponentShowcase/components/TokenHexLabel/index.tsx b/src/modules/dev/ComponentShowcase/components/TokenHexLabel/index.tsx
new file mode 100644
index 0000000000..8a42e3f38e
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/TokenHexLabel/index.tsx
@@ -0,0 +1,33 @@
+import { Typography } from '@mui/material';
+import { FigmaColorName } from 'src/utils/figmaColors';
+import { darkScheme } from 'src/utils/theme';
+
+import { HEX_TEXT, tokenHex } from '../../utils/tokenHex';
+
+// The scheme being rendered gets fg-2, the other fg-4 — so it's unambiguous which value the swatch
+// above is actually painting, while both stay readable for checking against Figma without toggling.
+// Hoisted: neither depends on props, and every swatch on the colors page renders two of them.
+const ACTIVE = { ...HEX_TEXT, display: 'block', color: 'fg-2', ...darkScheme({ color: 'fg-4' }) };
+const INACTIVE = { ...HEX_TEXT, display: 'block', color: 'fg-4', ...darkScheme({ color: 'fg-2' }) };
+
+/**
+ * Token name plus BOTH modes' source values. Shared by every specimen that labels a color token, so
+ * the showcase reports hexes one way instead of one way per component.
+ */
+export const TokenHexLabel = ({ name }: { name: FigmaColorName }) => {
+ const { light, dark } = tokenHex(name);
+
+ return (
+ <>
+
+ {name}
+
+
+ {light}
+
+
+ {dark}
+
+ >
+ );
+};
diff --git a/src/modules/dev/ComponentShowcase/components/TypographySection/index.tsx b/src/modules/dev/ComponentShowcase/components/TypographySection/index.tsx
new file mode 100644
index 0000000000..cdba8388b2
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/components/TypographySection/index.tsx
@@ -0,0 +1,17 @@
+import { Typography } from '@mui/material';
+
+import { TYPOGRAPHY_VARIANTS } from '../../utils/catalog';
+import { Section } from '../Section';
+import { Specimen } from '../Specimen';
+
+export const TypographySection = () => (
+
+ {TYPOGRAPHY_VARIANTS.map((variant) => (
+
+
+ The quick brown fox jumps over the lazy dog — 1234567890
+
+
+ ))}
+
+);
diff --git a/src/modules/dev/ComponentShowcase/utils/catalog.ts b/src/modules/dev/ComponentShowcase/utils/catalog.ts
new file mode 100644
index 0000000000..d41a7d1af1
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/utils/catalog.ts
@@ -0,0 +1,62 @@
+import { TypographyProps } from '@mui/material';
+import { FigmaColorName } from 'src/utils/figmaColors';
+
+type TypographyVariant = TypographyProps['variant'];
+
+// The typography variants enabled in the theme. The default MUI variants
+// (body1/body2/button/subtitle*/h6/overline) are disabled in theme.tsx, so
+// they are intentionally omitted here.
+export const TYPOGRAPHY_VARIANTS: TypographyVariant[] = [
+ 'display1',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'subheader1',
+ 'subheader2',
+ 'description',
+ 'caption',
+ 'secondary21',
+ 'secondary16',
+ 'main12',
+ 'buttonL',
+ 'buttonM',
+ 'buttonS',
+ 'helperText',
+];
+
+export type ColorRole = 'bg' | 'text' | 'border' | 'shadow' | 'swatch';
+
+// Figma color tokens grouped for the showcase. `role` decides how each group is presented — text
+// colors as text, backgrounds as surfaces, borders as dividers, shadows as shadows. Names are keys
+// of `figmaLight`, so each resolves in both light and dark via the flattened palette tokens.
+export const COLOR_GROUPS: { title: string; role: ColorRole; names: FigmaColorName[] }[] = [
+ {
+ title: 'Backgrounds',
+ role: 'bg',
+ names: ['bg-max', 'bg-1', 'bg-2', 'bg-3', 'bg-4', 'bg-5', 'bg-6'],
+ },
+ {
+ title: 'Foreground / Text',
+ role: 'text',
+ names: ['fg-max', 'fg-1', 'fg-2', 'fg-3', 'fg-4', 'fg-5'],
+ },
+ {
+ title: 'Borders / dividers',
+ role: 'border',
+ names: ['border-0', 'border-1', 'border-2', 'border-opaque'],
+ },
+ {
+ title: 'Shadows',
+ role: 'shadow',
+ names: [
+ 'shadow-low',
+ 'shadow-medium',
+ 'shadow-high',
+ 'shadow-strong',
+ 'shadow-stroke-1',
+ 'shadow-stroke-2',
+ ],
+ },
+];
diff --git a/src/modules/dev/ComponentShowcase/utils/registry.tsx b/src/modules/dev/ComponentShowcase/utils/registry.tsx
new file mode 100644
index 0000000000..5f40ebde88
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/utils/registry.tsx
@@ -0,0 +1,118 @@
+import dynamic from 'next/dynamic';
+import { ComponentType } from 'react';
+
+export interface ShowcaseSection {
+ slug: string;
+ label: string;
+ group: string;
+ Component: ComponentType;
+ /** Opt out of the layout's max-width content container (e.g. full-width page banners). */
+ fullBleed?: boolean;
+}
+
+// One entry per route (`/dev/components/`). Each section is lazily loaded so a
+// given page only ships its own section's code — keeping every page light.
+export const SHOWCASE_SECTIONS: ShowcaseSection[] = [
+ {
+ slug: 'colors',
+ label: 'Colors',
+ group: 'Foundations',
+ Component: dynamic(() => import('../components/ColorsSection').then((m) => m.ColorsSection)),
+ },
+ {
+ slug: 'typography',
+ label: 'Typography',
+ group: 'Foundations',
+ Component: dynamic(() =>
+ import('../components/TypographySection').then((m) => m.TypographySection)
+ ),
+ },
+ {
+ slug: 'icons',
+ label: 'Icons',
+ group: 'Foundations',
+ Component: dynamic(() => import('../components/IconsSection').then((m) => m.IconsSection)),
+ },
+ {
+ slug: 'buttons',
+ label: 'Buttons',
+ group: 'Inputs & actions',
+ Component: dynamic(() => import('../components/ButtonsSection').then((m) => m.ButtonsSection)),
+ },
+ {
+ slug: 'form-controls',
+ label: 'Form controls',
+ group: 'Inputs & actions',
+ Component: dynamic(() =>
+ import('../components/FormControlsSection').then((m) => m.FormControlsSection)
+ ),
+ },
+ {
+ slug: 'toggles-badges',
+ label: 'Toggles & badges',
+ group: 'Inputs & actions',
+ Component: dynamic(() =>
+ import('../components/TogglesBadgesSection').then((m) => m.TogglesBadgesSection)
+ ),
+ },
+ {
+ slug: 'feedback',
+ label: 'Feedback',
+ group: 'Feedback & overlays',
+ Component: dynamic(() =>
+ import('../components/FeedbackSection').then((m) => m.FeedbackSection)
+ ),
+ },
+ {
+ slug: 'overlays',
+ label: 'Overlays & modal',
+ group: 'Feedback & overlays',
+ Component: dynamic(() =>
+ import('../components/OverlaysSection').then((m) => m.OverlaysSection)
+ ),
+ },
+ {
+ slug: 'empty-states',
+ label: 'Empty states',
+ group: 'Feedback & overlays',
+ Component: dynamic(() =>
+ import('../components/EmptyStatesSection').then((m) => m.EmptyStatesSection)
+ ),
+ },
+ {
+ slug: 'surfaces',
+ label: 'Surfaces & cards',
+ group: 'Data & surfaces',
+ Component: dynamic(() =>
+ import('../components/SurfacesSection').then((m) => m.SurfacesSection)
+ ),
+ },
+ {
+ slug: 'data-primitives',
+ label: 'Data primitives',
+ group: 'Data & surfaces',
+ Component: dynamic(() =>
+ import('../components/DataPrimitivesSection').then((m) => m.DataPrimitivesSection)
+ ),
+ },
+ {
+ slug: 'banners',
+ label: 'Banners',
+ group: 'Data & surfaces',
+ fullBleed: true,
+ Component: dynamic(() => import('../components/BannersSection').then((m) => m.BannersSection)),
+ },
+];
+
+// Sections grouped for the sidebar, preserving declaration order.
+export const SHOWCASE_GROUPS = SHOWCASE_SECTIONS.reduce<
+ { label: string; sections: ShowcaseSection[] }[]
+>((groups, section) => {
+ const group = groups.find((g) => g.label === section.group);
+ if (group) {
+ group.sections.push(section);
+ } else {
+ groups.push({ label: section.group, sections: [section] });
+ }
+ return groups;
+}, []);
diff --git a/src/modules/dev/ComponentShowcase/utils/tokenHex.ts b/src/modules/dev/ComponentShowcase/utils/tokenHex.ts
new file mode 100644
index 0000000000..4199bdf355
--- /dev/null
+++ b/src/modules/dev/ComponentShowcase/utils/tokenHex.ts
@@ -0,0 +1,23 @@
+import { FigmaColorName, pickFigma } from 'src/utils/figmaColors';
+
+const LIGHT = pickFigma('light');
+const DARK = pickFigma('dark');
+
+/**
+ * Normalise a token's source value for display: hex uppercased to match how Figma shows it,
+ * `rgba()`/`hsl()` left exactly as authored. `figmaColors.ts` has mixed casing (`#18181B` next to
+ * `#0a0a0a`), so without this the same token can read differently on two showcase pages.
+ */
+const formatColor = (value: string) => (value.startsWith('#') ? value.toUpperCase() : value);
+
+/** Monospace so hex digits line up when scanning a column of tokens. */
+export const HEX_TEXT = {
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
+ letterSpacing: 0,
+} as const;
+
+/** Both modes' source values for a token, display-normalised. */
+export const tokenHex = (name: FigmaColorName) => ({
+ light: formatColor(LIGHT[name]),
+ dark: formatColor(DARK[name]),
+});
diff --git a/src/modules/faucet/FaucetAssetsList.tsx b/src/modules/faucet/FaucetAssetsList.tsx
index f952364f34..49a805f415 100644
--- a/src/modules/faucet/FaucetAssetsList.tsx
+++ b/src/modules/faucet/FaucetAssetsList.tsx
@@ -2,8 +2,8 @@ import { valueToBigNumber } from '@aave/math-utils';
import { ExternalLinkIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
import { Box, Button, SvgIcon, Typography, useMediaQuery, useTheme } from '@mui/material';
-import * as React from 'react';
import { ConnectWalletPaper } from 'src/components/ConnectWalletPaper';
+import { LIST_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
@@ -19,7 +19,6 @@ import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { FaucetItemLoader } from './FaucetItemLoader';
-import { FaucetMobileItemLoader } from './FaucetMobileItemLoader';
export default function FaucetAssetsList() {
const { reserves, loading } = useAppDataContext();
@@ -30,7 +29,7 @@ export default function FaucetAssetsList() {
const { walletBalances } = useWalletBalances(currentMarketData);
const theme = useTheme();
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ const showCards = useMediaQuery(theme.breakpoints.down(LIST_CARDS_BELOW));
const listData = reserves
.filter(
@@ -62,14 +61,14 @@ export default function FaucetAssetsList() {
}
>
-
+ Asset
- {!downToXSM && (
+ {!showCards && (
Wallet balance
@@ -81,11 +80,11 @@ export default function FaucetAssetsList() {
{loading ? (
- downToXSM ? (
+ showCards ? (
<>
-
-
-
+
+
+
>
) : (
<>
@@ -99,7 +98,7 @@ export default function FaucetAssetsList() {
) : (
listData.map((reserve) => (
@@ -114,24 +113,20 @@ export default function FaucetAssetsList() {
{reserve.name}
-
+
{reserve.symbol}
- {!downToXSM && (
+ {!showCards && (
-
+
)}
-
+
{!currentMarketData.addresses.FAUCET ? (
{
+export const FaucetItemLoader = ({ compact }: { compact?: boolean }) => {
return (
-
+
-
+
-
-
-
+ {!compact && (
+
+
+
+ )}
-
+ Faucet
diff --git a/src/modules/faucet/FaucetMobileItemLoader.tsx b/src/modules/faucet/FaucetMobileItemLoader.tsx
deleted file mode 100644
index b5dd5efefd..0000000000
--- a/src/modules/faucet/FaucetMobileItemLoader.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Trans } from '@lingui/macro';
-import { Box, Button, Skeleton } from '@mui/material';
-
-import { ListColumn } from '../../components/lists/ListColumn';
-import { ListItem } from '../../components/lists/ListItem';
-
-export const FaucetMobileItemLoader = () => {
- return (
-
-
-
-
-
-
-
-
-
- Faucet
-
-
-
- );
-};
diff --git a/src/modules/faucet/FaucetTopPanel.tsx b/src/modules/faucet/FaucetTopPanel.tsx
index 47c3e0de91..1ae7505665 100644
--- a/src/modules/faucet/FaucetTopPanel.tsx
+++ b/src/modules/faucet/FaucetTopPanel.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
-import * as React from 'react';
+import { Typography } from '@mui/material';
import { Link } from 'src/components/primitives/Link';
import { PageTitle } from 'src/components/TopInfoPanel/PageTitle';
import { useRootStore } from 'src/store/root';
@@ -8,37 +7,31 @@ import { useRootStore } from 'src/store/root';
import { TopInfoPanel } from '../../components/TopInfoPanel/TopInfoPanel';
export const FaucetTopPanel = () => {
- const { breakpoints } = useTheme();
- const md = useMediaQuery(breakpoints.down('md'));
- const xsm = useMediaQuery(breakpoints.down('xsm'));
const currentMarketData = useRootStore((store) => store.currentMarketData);
return (
>}
titleComponent={
-
+ <>
{currentMarketData.marketTitle} Faucet}
withMarketSwitcher={true}
/>
-
-
-
- With testnet Faucet you can get free assets to test the Aave Protocol. Make sure to
- switch your wallet provider to the appropriate testnet network, select desired
- asset, and click ‘Faucet’ to get tokens transferred to your wallet. The assets on a
- testnet are not “real,” meaning they have no monetary value.{' '}
-
- Learn more
-
-
-
-
-
+
+
+ With testnet Faucet you can get free assets to test the Aave Protocol. Make sure to
+ switch your wallet provider to the appropriate testnet network, select desired asset,
+ and click ‘Faucet’ to get tokens transferred to your wallet. The assets on a testnet
+ are not “real,” meaning they have no monetary value.{' '}
+
+ Learn more
+
+
+
+ >
}
/>
);
diff --git a/src/modules/governance/DelegatedInfoPanel.tsx b/src/modules/governance/DelegatedInfoPanel.tsx
index 85065eff8a..bfe37afcea 100644
--- a/src/modules/governance/DelegatedInfoPanel.tsx
+++ b/src/modules/governance/DelegatedInfoPanel.tsx
@@ -13,6 +13,7 @@ import { usePowers } from 'src/hooks/governance/usePowers';
import { useModalContext } from 'src/hooks/useModal';
import { ZERO_ADDRESS } from 'src/modules/governance/utils/formatProposal';
import { useRootStore } from 'src/store/root';
+import { cardHeadingSx } from 'src/utils/cardStyles';
import { GENERAL } from 'src/utils/events';
type DelegatedPowerProps = {
@@ -42,7 +43,7 @@ const DelegatedPower: React.FC = ({
return (
-
+ {title}
@@ -64,7 +65,7 @@ const DelegatedPower: React.FC = ({
value={Number(aavePower) + Number(stkAavePower) + Number(aAavePower)}
variant="subheader1"
/>
-
+
AAVE + stkAAVE + aAAVE
@@ -161,12 +162,12 @@ export const DelegatedInfoPanel = () => {
powers.aAavePropositionDelegatee !== constants.AddressZero;
return (
-
+
-
+ Delegated power
-
+
Use your AAVE, stkAAVE, or aAave balance to delegate your voting and proposition powers.
You will not be sending any tokens, only the rights to vote and propose changes to the
@@ -176,7 +177,7 @@ export const DelegatedInfoPanel = () => {
href="https://docs.aave.com/developers/v/2.0/protocol-governance/governance"
target="_blank"
variant="description"
- color="text.secondary"
+ color="fg-2"
sx={{ textDecoration: 'underline', ml: 1 }}
onClick={() => trackEvent(GENERAL.EXTERNAL_LINK, { link: 'Learn More Delegation' })}
>
@@ -184,7 +185,7 @@ export const DelegatedInfoPanel = () => {
{disableButton ? (
-
+ You have no AAVE/stkAAVE/aAave balance to delegate.
) : (
@@ -212,20 +213,13 @@ export const DelegatedInfoPanel = () => {
- openGovDelegation()}
- >
+ openGovDelegation()}>
Set up delegation
{showRevokeButton && (
openRevokeGovDelegation()}
>
diff --git a/src/modules/governance/FormattedProposalTime.tsx b/src/modules/governance/FormattedProposalTime.tsx
index 0a14b77256..905f6d7a2d 100644
--- a/src/modules/governance/FormattedProposalTime.tsx
+++ b/src/modules/governance/FormattedProposalTime.tsx
@@ -27,11 +27,7 @@ export function FormattedProposalTime({
if ([ProposalState.Pending].includes(state)) {
return (
-
+
{state}
starts
@@ -43,11 +39,7 @@ export function FormattedProposalTime({
if ([ProposalState.Active].includes(state)) {
return (
-
+
{state}
ends
@@ -67,11 +59,7 @@ export function FormattedProposalTime({
) {
return (
-
+
{state}
on
@@ -85,11 +73,7 @@ export function FormattedProposalTime({
const canBeExecuted = timestamp > executionTime;
return (
-
+
{canBeExecuted ? Expires : Can be executed}
diff --git a/src/modules/governance/GovernanceTopPanel.tsx b/src/modules/governance/GovernanceTopPanel.tsx
index 4444a46959..fa40cf2a59 100644
--- a/src/modules/governance/GovernanceTopPanel.tsx
+++ b/src/modules/governance/GovernanceTopPanel.tsx
@@ -1,9 +1,8 @@
import { ChainId } from '@aave/contract-helpers';
-import { ExternalLinkIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import { Box, Button, SvgIcon, Typography, useMediaQuery, useTheme } from '@mui/material';
-import * as React from 'react';
+import { Box, Typography } from '@mui/material';
import { ChainAvailabilityText } from 'src/components/ChainAvailabilityText';
+import { ExternalLinkButton } from 'src/components/ExternalLinkButton';
import { Link } from 'src/components/primitives/Link';
import { useRootStore } from 'src/store/root';
import { GENERAL } from 'src/utils/events';
@@ -19,30 +18,16 @@ function ExternalLink({ text, href }: ExternalLinkProps) {
const trackEvent = useRootStore((store) => store.trackEvent);
return (
- trackEvent(GENERAL.EXTERNAL_LINK, { Link: text })}
>
-
- {text}
-
-
-
-
-
+ {text}
+
);
}
export const GovernanceTopPanel = () => {
- const theme = useTheme();
- const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
const trackEvent = useRootStore((store) => store.trackEvent);
return (
@@ -51,16 +36,12 @@ export const GovernanceTopPanel = () => {
- {/* */}
-
+ Aave Governance
-
+
Aave is a fully decentralized, community governed protocol by the AAVE token-holders.
AAVE token-holders collectively discuss, propose, and vote on upgrades to the
@@ -70,7 +51,7 @@ export const GovernanceTopPanel = () => {
trackEvent(GENERAL.EXTERNAL_LINK, { Link: 'FAQ Docs Governance' })}
href="https://aave.com/docs/ecosystem/governance"
- sx={{ textDecoration: 'underline', color: '#8E92A3' }}
+ sx={{ textDecoration: 'underline', color: 'fg-3' }}
>
documentation
@@ -83,15 +64,15 @@ export const GovernanceTopPanel = () => {
sx={{
display: 'flex',
alignItems: 'center',
- gap: '16px',
+ gap: '0.5rem',
flexWrap: 'wrap',
maxWidth: 'sm',
}}
>
-
-
+
+
-
+
);
diff --git a/src/modules/governance/ProposalListHeader.tsx b/src/modules/governance/ProposalListHeader.tsx
index d6ef45e8bf..86b3ab6ea9 100644
--- a/src/modules/governance/ProposalListHeader.tsx
+++ b/src/modules/governance/ProposalListHeader.tsx
@@ -34,13 +34,16 @@ export const ProposalListHeaderDesktop: React.FC
}) => {
return (
<>
-
+ Proposals
-
- Filter
-
-
@@ -233,7 +237,7 @@ export const ProposalOverview = ({ proposal, loading, error }: ProposalOverviewP
component="blockquote"
sx={{
borderLeft: '4px solid',
- borderColor: 'divider',
+ borderColor: 'border-2',
pl: 4,
my: 3,
ml: 0,
@@ -251,7 +255,7 @@ export const ProposalOverview = ({ proposal, loading, error }: ProposalOverviewP
);
},
diff --git a/src/modules/governance/proposal/ProposalPayloads.tsx b/src/modules/governance/proposal/ProposalPayloads.tsx
index f406f1e3e2..c0eef3f5fe 100644
--- a/src/modules/governance/proposal/ProposalPayloads.tsx
+++ b/src/modules/governance/proposal/ProposalPayloads.tsx
@@ -7,6 +7,7 @@ import { textCenterEllipsis } from 'src/helpers/text-center-ellipsis';
import { getSeatbeltReportUrl } from 'src/modules/governance/utils/seatbelt';
import { ProposalPayload } from 'src/services/governance-cache-sdk';
import { networkConfigs } from 'src/ui-config/networksConfig';
+import { cardHeadingSx, cardPaddingSx } from 'src/utils/cardStyles';
import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig';
interface ProposalPayloadsProps {
@@ -79,7 +80,7 @@ export const ProposalPayloads = ({ payloads, loading }: ProposalPayloadsProps) =
if (loading) {
return (
-
+
);
@@ -91,8 +92,8 @@ export const ProposalPayloads = ({ payloads, loading }: ProposalPayloadsProps) =
const visiblePayloads = expanded ? payloads : payloads.slice(0, COLLAPSED_COUNT);
return (
-
-
+
+ Payloads
@@ -112,7 +113,7 @@ export const ProposalPayloads = ({ payloads, loading }: ProposalPayloadsProps) =
{logo && }
-
+ Payload {p.payloadId}
diff --git a/src/modules/governance/proposal/ProposalTimeline.tsx b/src/modules/governance/proposal/ProposalTimeline.tsx
index 3485f0a873..c54f4d0741 100644
--- a/src/modules/governance/proposal/ProposalTimeline.tsx
+++ b/src/modules/governance/proposal/ProposalTimeline.tsx
@@ -1,6 +1,7 @@
import { ExternalLinkIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import { KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
+import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp';
import {
Avatar,
Box,
@@ -19,6 +20,8 @@ import { useGovernanceCoreConstants } from 'src/hooks/governance/useGovernanceCo
import { ProposalDetail, ProposalPayload } from 'src/services/GovernanceCacheService';
import { governanceV3Config } from 'src/ui-config/governanceConfig';
import { networkConfigs } from 'src/ui-config/networksConfig';
+import { cardHeadingSx, cardPaddingSx } from 'src/utils/cardStyles';
+import { figVars } from 'src/utils/figmaColors';
import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig';
// Single-spine proposal timeline. It weaves the three state machines (proposal core, voting machine,
@@ -109,16 +112,16 @@ interface Step {
const statusColor = (status: StepStatus, theme: Theme) => {
switch (status) {
case 'done':
- return theme.palette.primary.main;
+ return theme.vars.palette.primary.main;
case 'ok':
- return theme.palette.success.main;
+ return theme.vars.palette.success.main;
case 'now':
case 'settled':
- return theme.palette.warning.main;
+ return theme.vars.palette.warning.main;
case 'terminal':
- return theme.palette.error.main;
+ return theme.vars.palette.error.main;
default:
- return theme.palette.text.disabled;
+ return figVars['fg-4'];
}
};
@@ -505,7 +508,7 @@ export const ProposalTimeline = ({
if (!proposal || payloadsLoading) {
return (
-
+
);
@@ -525,7 +528,7 @@ export const ProposalTimeline = ({
}}
>
{logo && }
-
+
{sub.label}
@@ -538,10 +541,10 @@ export const ProposalTimeline = ({
sub.tone === 'ready' || sub.tone === 'countdown'
? 'warning.main'
: sub.tone === 'done'
- ? 'text.muted'
+ ? 'fg-3'
: sub.tone === 'estimate'
- ? 'text.muted'
- : 'text.secondary',
+ ? 'fg-3'
+ : 'fg-2',
whiteSpace: 'nowrap',
}}
>
@@ -565,8 +568,8 @@ export const ProposalTimeline = ({
};
return (
-
-
+
+ Timeline
@@ -608,10 +611,10 @@ export const ProposalTimeline = ({
step.status === 'settled' ||
step.status === 'terminal'
? color
- : theme.palette.background.paper,
+ : theme.vars.palette.background.paper,
boxShadow:
step.status === 'now'
- ? `0 0 0 4px ${theme.palette.background.paper}, 0 0 0 6px ${theme.palette.warning.main}33`
+ ? `0 0 0 4px ${theme.vars.palette.background.paper}, 0 0 0 6px ${theme.vars.palette.warning.main}33`
: undefined,
}}
/>
@@ -627,7 +630,7 @@ export const ProposalTimeline = ({
gap: 2,
cursor: hasSubs ? 'pointer' : 'default',
'&:focus-visible': {
- outline: `2px solid ${theme.palette.primary.main}`,
+ outline: `2px solid ${theme.vars.palette.primary.main}`,
outlineOffset: 2,
borderRadius: 1,
},
@@ -647,14 +650,14 @@ export const ProposalTimeline = ({
}
>
{step.name}
{hasSubs && (
-
+
{isOpen ? (
) : (
@@ -688,7 +691,7 @@ export const ProposalTimeline = ({
step.status === 'ok'
? 'success.main'
: step.status === 'pending'
- ? 'text.muted'
+ ? 'fg-3'
: 'warning.main',
}}
>
@@ -702,8 +705,8 @@ export const ProposalTimeline = ({
step.valueKind === 'countdown'
? 'warning.main'
: step.valueKind === 'pending'
- ? 'text.disabled'
- : 'text.muted',
+ ? 'fg-4'
+ : 'fg-3',
fontWeight: step.valueKind === 'countdown' ? 600 : 400,
fontStyle:
step.valueKind === 'pending' || step.valueKind === 'estimate'
diff --git a/src/modules/governance/proposal/ProposalTopPanel.tsx b/src/modules/governance/proposal/ProposalTopPanel.tsx
index d2dffd83e6..933d1f5b87 100644
--- a/src/modules/governance/proposal/ProposalTopPanel.tsx
+++ b/src/modules/governance/proposal/ProposalTopPanel.tsx
@@ -1,35 +1,30 @@
import { ArrowLeftIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Box, Button, SvgIcon } from '@mui/material';
-import * as React from 'react';
+import { Button, Container, SvgIcon } from '@mui/material';
import { Link, ROUTES } from 'src/components/primitives/Link';
import { useRootStore } from 'src/store/root';
import { AIP } from 'src/utils/events';
-import { TopInfoPanel } from '../../../components/TopInfoPanel/TopInfoPanel';
-
export const ProposalTopPanel = () => {
const trackEvent = useRootStore((store) => store.trackEvent);
return (
-
-
- trackEvent(AIP.GO_BACK)}
- color="primary"
- startIcon={
-
-
-
- }
- >
- Go Back
-
-
-
+
+ trackEvent(AIP.GO_BACK)}
+ color="primary"
+ startIcon={
+
+
+
+ }
+ >
+ Go Back
+
+
);
};
diff --git a/src/modules/governance/proposal/VoteInfo.tsx b/src/modules/governance/proposal/VoteInfo.tsx
index cb428c09a1..ffb57ece38 100644
--- a/src/modules/governance/proposal/VoteInfo.tsx
+++ b/src/modules/governance/proposal/VoteInfo.tsx
@@ -1,16 +1,16 @@
import { VotingMachineProposalState } from '@aave/contract-helpers';
import { Trans } from '@lingui/macro';
-import { Box, Button, Paper, Typography } from '@mui/material';
+import { Alert, AlertTitle, Box, Button, Paper, Typography } from '@mui/material';
import { constants } from 'ethers';
import { formatUnits } from 'ethers/lib/utils';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Row } from 'src/components/primitives/Row';
-import { Warning } from 'src/components/primitives/Warning';
import { ConnectWalletButton } from 'src/components/WalletConnection/ConnectWalletButton';
import { useVotingPowerAt } from 'src/hooks/governance/useVotingPowerAt';
import { useModalContext } from 'src/hooks/useModal';
import { VoteProposalData } from 'src/modules/governance/types';
import { useRootStore } from 'src/store/root';
+import { cardHeadingSx, cardPaddingSx } from 'src/utils/cardStyles';
import { networkConfigs } from '../../../ui-config/networksConfig';
@@ -40,12 +40,12 @@ export function VoteInfo({ voteData }: VoteInfoProps) {
powerAtProposalStart && !didVote && !!user && voteOngoing && Number(powerAtProposalStart) !== 0;
return (
-
+
-
+ Your voting info
{network && (
@@ -53,7 +53,7 @@ export function VoteInfo({ voteData }: VoteInfoProps) {
sx={{
display: 'flex',
alignItems: 'center',
- color: 'text.secondary',
+ color: 'fg-2',
}}
>
@@ -83,7 +83,7 @@ export function VoteInfo({ voteData }: VoteInfoProps) {
{user ? (
<>
{user && !didVote && !voteOngoing && (
-
+ You did not participate in this proposal
)}
@@ -94,40 +94,37 @@ export function VoteInfo({ voteData }: VoteInfoProps) {
Voting power
-
+
(AAVE + stkAAVE)
>
}
>
-
+
)}
{showAlreadyVotedMsg && voteOnProposal && (
-
-
+
+ You voted {voteOnProposal.support ? 'YAE' : 'NAY'}
-
-
-
- With a voting power of{' '}
-
-
-
-
+
+
+ With a voting power of{' '}
+
+
+
)}
{showCannotVoteMsg && (
-
+ Not enough voting power to participate in this proposal
-
+
)}
{showCanVoteMsg && (
<>
@@ -136,6 +133,7 @@ export function VoteInfo({ voteData }: VoteInfoProps) {
variant="contained"
fullWidth
onClick={() => openGovVote(voteData, true, powerAtProposalStart)}
+ sx={{ mt: 4 }}
>
Vote YAE
diff --git a/src/modules/governance/proposal/VotersList.tsx b/src/modules/governance/proposal/VotersList.tsx
index 7521e6a4c6..fd812b8ce0 100644
--- a/src/modules/governance/proposal/VotersList.tsx
+++ b/src/modules/governance/proposal/VotersList.tsx
@@ -11,11 +11,26 @@ type VotersListProps = {
sx?: SxProps;
};
+const VISIBLE_HEIGHT = 205;
+const GUTTER = 16;
+const FADE = '0.75rem';
+const SCROLL_FADE = `linear-gradient(to bottom, transparent, #000 ${FADE}, #000 calc(100% - ${FADE}), transparent)`;
+
export const VotersList = ({ compact = false, voters, sx }: VotersListProps): JSX.Element => {
return (
-
+
{voters.length === 0 ? (
- —
+ —
) : (
voters
.sort((a, b) => Number(b.votingPower) - Number(a.votingPower))
diff --git a/src/modules/governance/proposal/VotersListContainer.tsx b/src/modules/governance/proposal/VotersListContainer.tsx
index 2fb34c185b..59cafa998f 100644
--- a/src/modules/governance/proposal/VotersListContainer.tsx
+++ b/src/modules/governance/proposal/VotersListContainer.tsx
@@ -27,27 +27,21 @@ export const VotersListContainer = ({ voteInfo, voters }: VotersListProps): JSX.
if (!voters || voters.combinedVotes.length === 0) return ;
+ const hasMoreVoters = voters.combinedVotes.length > 10;
+
return (
-
-
-
- {voters.combinedVotes.length > 10 ? (
- Top 10 addresses
- ) : (
- Addresses
- )}
+
+
+
+ {hasMoreVoters ? Top 10 addresses : Addresses}
-
+ Votes
-
- {voters.combinedVotes.length > 10 && (
-
+
+ {hasMoreVoters && (
+ View all votes
)}
diff --git a/src/modules/governance/proposal/VotersListModal.tsx b/src/modules/governance/proposal/VotersListModal.tsx
index b4c5c4d496..c4e5cf4abb 100644
--- a/src/modules/governance/proposal/VotersListModal.tsx
+++ b/src/modules/governance/proposal/VotersListModal.tsx
@@ -2,8 +2,8 @@ import { Trans } from '@lingui/macro';
import { Box, Grid, Typography, useMediaQuery, useTheme } from '@mui/material';
import { useState } from 'react';
import { Row } from 'src/components/primitives/Row';
-import StyledToggleButton from 'src/components/StyledToggleButton';
-import StyledToggleButtonGroup from 'src/components/StyledToggleButtonGroup';
+import { StyledTxModalToggleButton } from 'src/components/StyledToggleButton';
+import { StyledTxModalToggleGroup } from 'src/components/StyledToggleButtonGroup';
import { ProposalVoteDisplayInfo, VotersSplitDisplay } from 'src/modules/governance/types';
import { BasicModal } from '../../../components/primitives/BasicModal';
@@ -28,7 +28,7 @@ export const VotersListModal = ({
const [voteView, setVoteView] = useState<'yaes' | 'nays'>('yaes');
const borderBaseStyle = {
border: '1px solid',
- borderColor: 'divider',
+ borderColor: 'border-0',
borderRadius: 1,
};
@@ -52,13 +52,13 @@ export const VotersListModal = ({
px: 4,
py: 2,
borderBottom: '1px solid',
- borderColor: 'divider',
+ borderColor: 'border-0',
}}
>
-
+ Addresses ({voters.yaeVotes.length})
-
+ Votes
@@ -91,13 +91,13 @@ export const VotersListModal = ({
px: 4,
py: 2,
borderBottom: '1px solid',
- borderColor: 'divider',
+ borderColor: 'border-0',
}}
>
-
+ Addresses ({voters.nayVotes.length})
-
+ Votes
@@ -129,24 +129,24 @@ export const VotersListModal = ({
) : (
<>
- setVoteView(value)}
- sx={{ width: '100%', height: '44px', mt: 8, mb: 6 }}
+ sx={{ width: '100%', mt: 8, mb: 6 }}
>
-
+ Voted YAE
-
-
+
+ Voted NAY
-
-
+
+
{voteView === 'yaes' && yesVotesUI}
{voteView === 'nays' && noVotesUI}
>
diff --git a/src/modules/governance/proposal/VotingResults.tsx b/src/modules/governance/proposal/VotingResults.tsx
index 9e0627f76a..784b112245 100644
--- a/src/modules/governance/proposal/VotingResults.tsx
+++ b/src/modules/governance/proposal/VotingResults.tsx
@@ -7,6 +7,7 @@ import { Link } from 'src/components/primitives/Link';
import { Row } from 'src/components/primitives/Row';
import { ProposalDetailDisplay, VotersSplitDisplay } from 'src/modules/governance/types';
import { useRootStore } from 'src/store/root';
+import { cardHeadingSx, cardPaddingSx } from 'src/utils/cardStyles';
import { GENERAL } from 'src/utils/events';
import { StateBadge } from '../StateBadge';
@@ -24,8 +25,8 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
const trackEvent = useRootStore((store) => store.trackEvent);
const discussionUrl = proposal?.discussions?.match(/https?:\/\/[^\s"]+/)?.[0];
return (
-
-
+
+ Voting results
{proposal ? (
@@ -76,7 +77,7 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
caption={
<>
Current votes
-
+
Required
>
@@ -96,7 +97,7 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
value={proposal.voteInfo.quorum}
visibleDecimals={2}
roundDown
- color="text.muted"
+ color="fg-3"
/>
@@ -123,7 +124,7 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
caption={
<>
Current differential
-
+
Required
>
@@ -143,7 +144,7 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
value={proposal.voteInfo.requiredDifferential}
visibleDecimals={2}
roundDown
- color="text.muted"
+ color="fg-3"
/>
@@ -159,7 +160,7 @@ export const VotingResults = ({ proposal, loading, voters, votesLoading }: Votin
})
}
href={discussionUrl}
- variant="outlined"
+ variant="tertiary"
fullWidth
endIcon={
diff --git a/src/modules/history/HistoryFilterMenu.tsx b/src/modules/history/HistoryFilterMenu.tsx
index f29114545c..a502cd0ccd 100644
--- a/src/modules/history/HistoryFilterMenu.tsx
+++ b/src/modules/history/HistoryFilterMenu.tsx
@@ -1,6 +1,7 @@
import { XCircleIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Check as CheckIcon, Sort as SortIcon } from '@mui/icons-material';
+import CheckIcon from '@mui/icons-material/Check';
+import SortIcon from '@mui/icons-material/Sort';
import {
Box,
Button,
@@ -16,6 +17,7 @@ import React, { useEffect, useState } from 'react';
import { DarkTooltip } from 'src/components/infoTooltips/DarkTooltip';
import { useRootStore } from 'src/store/root';
import { TRANSACTION_HISTORY } from 'src/utils/events';
+import { figVars } from 'src/utils/figmaColors';
import { FilterOptions } from './types';
@@ -117,7 +119,7 @@ export const HistoryFilterMenu: React.FC = ({
return (
-
+
TXs:
{displayedFilters}
@@ -144,7 +146,7 @@ export const HistoryFilterMenu: React.FC = ({
alignItems: 'center',
height: 36,
border: '1px solid',
- borderColor: 'divider',
+ borderColor: 'border-2',
borderRadius: '4px',
mr: downToMD ? 0 : 2,
ml: downToMD ? 4 : 0,
@@ -159,7 +161,7 @@ export const HistoryFilterMenu: React.FC = ({
= ({
{!allSelected && (
+ Reset
}
@@ -190,7 +192,9 @@ export const HistoryFilterMenu: React.FC = ({
}}
onClick={handleClearFilter}
>
-
+
+
+
)}
@@ -212,12 +216,12 @@ export const HistoryFilterMenu: React.FC = ({
-
}
/>
diff --git a/src/modules/history/HistoryWrapper.tsx b/src/modules/history/HistoryWrapper.tsx
index e12aa5521b..2ce0a92544 100644
--- a/src/modules/history/HistoryWrapper.tsx
+++ b/src/modules/history/HistoryWrapper.tsx
@@ -137,7 +137,7 @@ export const HistoryWrapper = () => {
Transactions
-
+ This list may not include all your swaps.
@@ -171,7 +171,7 @@ export const HistoryWrapper = () => {
-
+ .CSV
@@ -189,7 +189,7 @@ export const HistoryWrapper = () => {
-
+ .JSON
@@ -206,7 +206,7 @@ export const HistoryWrapper = () => {
.sort((a, b) => new Date(b[0]).getTime() - new Date(a[0]).getTime())
.map(([date, txns], groupIndex) => (
-
+
{date}
{txns.map((transaction: TransactionHistoryItemUnion, index: number) => {
@@ -234,17 +234,17 @@ export const HistoryWrapper = () => {
my: 24,
}}
>
-
+ Nothing found
-
+
We couldn't find any transactions related to your search. Try again with a
different asset name, or reset filters.
{
setSearchQuery('');
setFilterQuery([]);
@@ -266,7 +266,7 @@ export const HistoryWrapper = () => {
flex: 1,
}}
>
-
+
{currentMarket === 'proto_plasma_v3' ? (
Transaction history for Plasma not supported yet, coming soon.
) : (
diff --git a/src/modules/history/HistoryWrapperMobile.tsx b/src/modules/history/HistoryWrapperMobile.tsx
index e5ffc71a28..a52164b700 100644
--- a/src/modules/history/HistoryWrapperMobile.tsx
+++ b/src/modules/history/HistoryWrapperMobile.tsx
@@ -18,7 +18,7 @@ import { applyTxHistoryFilters, useTransactionHistory } from 'src/hooks/useTrans
import { downloadData, formatTransactionData, groupByDate } from './helpers';
import { HistoryFilterMenu } from './HistoryFilterMenu';
-import { HistoryMobileItemLoader } from './HistoryMobileItemLoader';
+import { HistoryItemLoader } from './HistoryItemLoader';
import TransactionMobileRowItem from './TransactionMobileRowItem';
import { FilterOptions, TransactionHistoryItemUnion } from './types';
@@ -172,7 +172,7 @@ export const HistoryWrapperMobile = () => {
open={Boolean(menuAnchorEl)}
onClose={handleDownloadMenuClose}
>
-
+ Export data to
+
);
};
diff --git a/src/modules/markets/MarketAssetsListItem.tsx b/src/modules/markets/MarketAssetsListItem.tsx
index f4fce15f03..5576329da6 100644
--- a/src/modules/markets/MarketAssetsListItem.tsx
+++ b/src/modules/markets/MarketAssetsListItem.tsx
@@ -56,7 +56,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
return (
{
trackEvent(MARKETS.DETAILS_NAVIGATION, {
@@ -76,7 +76,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
-
+
{name || reserve.underlyingToken.name}
@@ -85,7 +85,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
p: { xs: '0', xsm: '3.625px 0px' },
}}
>
-
+
{reserve.underlyingToken.symbol}
{reserve.isolationModeConfig?.canBeCollateral && (
@@ -101,7 +101,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
-
+
@@ -111,8 +111,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
incentives={reserve.supplyProtocolIncentives}
address={reserve.aToken.address}
symbol={reserve.underlyingToken.symbol}
- variant="main16"
- symbolsVariant="secondary16"
+ variant="h5"
tooltip={
<>
{externalIncentivesTooltipsSupplySide.superFestRewards && }
@@ -131,12 +130,12 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
{' '}
>
) : (
-
+
)}
@@ -150,8 +149,7 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
incentives={reserve.borrowProtocolIncentives}
address={reserve.vToken.address}
symbol={reserve.underlyingToken.symbol}
- variant="main16"
- symbolsVariant="secondary16"
+ variant="h5"
tooltip={
<>
{externalIncentivesTooltipsBorrowSide.superFestRewards && }
@@ -167,9 +165,9 @@ export const MarketAssetsListItem = ({ ...reserve }: ReserveWithProtocolIncentiv
reserve.borrowInfo.total.amount.value !== '0' && }
-
+ {
return (
-
+
-
+
@@ -33,7 +33,7 @@ export const MarketAssetsListItemLoader = () => {
-
+
diff --git a/src/modules/markets/MarketAssetsListMobileItem.tsx b/src/modules/markets/MarketAssetsListMobileItem.tsx
index 41e5325b7f..09d11e887a 100644
--- a/src/modules/markets/MarketAssetsListMobileItem.tsx
+++ b/src/modules/markets/MarketAssetsListMobileItem.tsx
@@ -62,23 +62,15 @@ export const MarketAssetsListMobileItem = ({ ...reserve }: ReserveWithProtocolIn
}}
>
Total supplied} captionVariant="description" mb={3}>
-
-
-
+
+
+ Supply APY}
captionVariant="description"
- mb={3}
+ mb="1rem"
align="flex-start"
>
{externalIncentivesTooltipsSupplySide.superFestRewards && }
@@ -100,29 +92,21 @@ export const MarketAssetsListMobileItem = ({ ...reserve }: ReserveWithProtocolIn
/>
-
+ Total borrowed} captionVariant="description" mb={3}>
-
+
{reserve.borrowInfo && Number(reserve.borrowInfo?.total.amount.value) > 0 ? (
<>
-
+
>
) : (
-
+
)}
@@ -135,7 +119,7 @@ export const MarketAssetsListMobileItem = ({ ...reserve }: ReserveWithProtocolIn
/>
}
captionVariant="description"
- mb={3}
+ mb="1rem"
align="flex-start"
>
{externalIncentivesTooltipsBorrowSide.superFestRewards && }
@@ -164,7 +148,7 @@ export const MarketAssetsListMobileItem = ({ ...reserve }: ReserveWithProtocolIn
reserve.borrowInfo.total.amount.value !== '0' && }
{
const { market, totalBorrows, loading } = useAppDataContext();
- const theme = useTheme();
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsVariant = downToSM ? 'secondary16' : 'secondary21';
return (
- }
containerProps={marketContainerProps}
- pageTitle={Markets}
- withMarketSwitcher
- withFavoriteButton
>
- Total market size} loading={loading}>
+ Total market size} loading={loading}>
-
- Total available} loading={loading}>
+
+ Total available} loading={loading}>
-
- Total borrows} loading={loading}>
+
+ Total borrows} loading={loading}>
-
-
+
+
);
};
diff --git a/src/modules/markets/utils/assetCategories.ts b/src/modules/markets/utils/assetCategories.ts
index 3f3ca40db9..3586bf8749 100644
--- a/src/modules/markets/utils/assetCategories.ts
+++ b/src/modules/markets/utils/assetCategories.ts
@@ -65,3 +65,21 @@ export const isAssetInCategoryDynamic = (
ethCorrelatedCoinGeckoSymbols
).includes(category);
};
+
+// Category-filter predicate shared by the market / dashboard / staking asset lists: an asset matches
+// when nothing is selected (show all) or it falls into at least one selected category.
+export const matchesSelectedCategories = (
+ symbol: string,
+ selectedCategories: AssetCategory[],
+ stablecoinCoinGeckoSymbols: string[] = [],
+ ethCorrelatedCoinGeckoSymbols: string[] = []
+): boolean =>
+ selectedCategories.length === 0 ||
+ selectedCategories.some((category) =>
+ isAssetInCategoryDynamic(
+ symbol,
+ category,
+ stablecoinCoinGeckoSymbols,
+ ethCorrelatedCoinGeckoSymbols
+ )
+ );
diff --git a/src/modules/migration/HFChange.tsx b/src/modules/migration/HFChange.tsx
index 719ef46690..9705604574 100644
--- a/src/modules/migration/HFChange.tsx
+++ b/src/modules/migration/HFChange.tsx
@@ -22,12 +22,12 @@ export const HFChange = ({ caption, hfCurrent, hfAfter, loading }: HFChangeProps
{!loading ? : }
-
+
{!loading ? : }
-
+ Liquidation at
{' <1.0'}
diff --git a/src/modules/migration/MigrationBottomPanel.tsx b/src/modules/migration/MigrationBottomPanel.tsx
index fbc9081ead..79d7412520 100644
--- a/src/modules/migration/MigrationBottomPanel.tsx
+++ b/src/modules/migration/MigrationBottomPanel.tsx
@@ -1,8 +1,9 @@
import { valueToBigNumber } from '@aave/math-utils';
import { ExclamationIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { ArrowDownward } from '@mui/icons-material';
+import ArrowDownward from '@mui/icons-material/ArrowDownward';
import {
+ Alert,
Box,
Button,
Checkbox,
@@ -15,7 +16,6 @@ import {
} from '@mui/material';
import { useState } from 'react';
import { Row } from 'src/components/primitives/Row';
-import { Warning } from 'src/components/primitives/Warning';
import { IsolationModeWarning } from 'src/components/transactions/Warnings/IsolationModeWarning';
import { UserSummaryAfterMigration } from 'src/hooks/migration/useUserSummaryAfterMigration';
import { UserSummaryAndIncentives } from 'src/hooks/pool/useUserSummaryAndIncentives';
@@ -180,8 +180,8 @@ export const MigrationBottomPanel = ({
/>
{blockingError !== null && (
-
+
-
+
)}
{enteringIsolationMode && }
@@ -214,7 +214,7 @@ export const MigrationBottomPanel = ({
diff --git a/src/modules/migration/MigrationIsolationWarning.tsx b/src/modules/migration/MigrationIsolationWarning.tsx
new file mode 100644
index 0000000000..906e30ecea
--- /dev/null
+++ b/src/modules/migration/MigrationIsolationWarning.tsx
@@ -0,0 +1,33 @@
+import { Trans } from '@lingui/macro';
+import { Alert, Box } from '@mui/material';
+import { Link, ROUTES } from 'src/components/primitives/Link';
+import { useRootStore } from 'src/store/root';
+import { IsolatedReserve } from 'src/store/v3MigrationSelectors';
+import { useShallow } from 'zustand/shallow';
+
+export const MigrationIsolationWarning = ({
+ isolatedReserveV3,
+}: {
+ isolatedReserveV3?: IsolatedReserve;
+}) => {
+ const [currentMarket, currentMarketData] = useRootStore(
+ useShallow((store) => [store.currentMarket, store.currentMarketData])
+ );
+
+ if (!isolatedReserveV3 || isolatedReserveV3.enteringIsolationMode) return null;
+
+ const marketName = currentMarketData.marketTitle;
+ const marketLink = ROUTES.dashboard + '/?marketName=' + currentMarket + '_v3';
+
+ return (
+
+
+
+ Some migrated assets will not be used as collateral due to enabled isolation mode in{' '}
+ {marketName} V3 Market. Visit {marketName} V3 Dashboard to
+ manage isolation mode.
+
+
+
+ );
+};
diff --git a/src/modules/migration/MigrationList.tsx b/src/modules/migration/MigrationList.tsx
index 61e45753da..faa051e50f 100644
--- a/src/modules/migration/MigrationList.tsx
+++ b/src/modules/migration/MigrationList.tsx
@@ -1,16 +1,14 @@
import { Trans } from '@lingui/macro';
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import { ReactNode } from 'react';
+import { TABLE_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
import { ListWrapper } from 'src/components/lists/ListWrapper';
-import { Link, ROUTES } from 'src/components/primitives/Link';
-import { Warning } from 'src/components/primitives/Warning';
-import { useRootStore } from 'src/store/root';
import { IsolatedReserve } from 'src/store/v3MigrationSelectors';
-import { useShallow } from 'zustand/shallow';
+import { MigrationIsolationWarning } from './MigrationIsolationWarning';
import { MigrationMobileList } from './MigrationMobileList';
import { MigrationSelectionBox } from './MigrationSelectionBox';
@@ -59,13 +57,7 @@ export const MigrationList = ({
isolatedReserveV3,
}: MigrationListProps) => {
const theme = useTheme();
- const [currentMarket, currentMarketData] = useRootStore(
- useShallow((store) => [store.currentMarket, store.currentMarketData])
- );
- const marketName = currentMarketData.marketTitle;
- const marketLink = ROUTES.dashboard + '/?marketName=' + currentMarket + '_v3';
-
- const isMobile = useMediaQuery(theme.breakpoints.down(1125));
+ const isMobile = useMediaQuery(theme.breakpoints.down(TABLE_CARDS_BELOW));
if (isMobile) {
return (
{children}
@@ -86,32 +79,19 @@ export const MigrationList = ({
return (
-
+
+
{titleComponent}
- {isolatedReserveV3 && !isolatedReserveV3.enteringIsolationMode && (
-
-
-
-
- Some migrated assets will not be used as collateral due to enabled isolation
- mode in {marketName} V3 Market. Visit{' '}
- {marketName} V3 Dashboard to manage isolation
- mode.
-
-
-
-
- )}
+
}
>
{(isAvailable || loading) && (
-
+ [store.currentMarket, store.currentMarketData])
);
- const isMobile = useMediaQuery(theme.breakpoints.down(1125));
+ const isMobile = useMediaQuery(theme.breakpoints.down(TABLE_CARDS_BELOW));
- const baseColor = disabled === undefined ? 'text.primary' : 'text.muted';
- const baseColorSecondary = disabled === undefined ? 'text.secondary' : 'text.muted';
+ const baseColor = disabled === undefined ? 'fg-1' : 'fg-3';
+ const baseColorSecondary = disabled === undefined ? 'fg-2' : 'fg-3';
const loadingRates = v3Rates?.ltv === undefined && v3Rates?.liquidationThreshold === undefined;
@@ -108,20 +110,18 @@ export const MigrationListItem = ({
-
+ ({
+ sx={{
border: `2px solid ${
- disabled !== undefined
- ? theme.palette.action.disabled
- : theme.palette.text.secondary
+ disabled !== undefined ? figVars['disabled-fg'] : figVars['fg-2']
}`,
background:
disabled !== undefined
- ? theme.palette.background.disabled
+ ? figVars['bg-6']
: checked
- ? theme.palette.text.secondary
- : theme.palette.background.paper,
+ ? figVars['fg-2']
+ : figVars['surface-elevated'],
width: 16,
height: 16,
borderRadius: '2px',
@@ -131,12 +131,12 @@ export const MigrationListItem = ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- })}
+ }}
onClick={disabled !== undefined ? undefined : onCheckboxClick}
data-cy={`migration-checkbox`}
>
{disabled === undefined && (
-
+
)}
@@ -167,7 +167,7 @@ export const MigrationListItem = ({
value={v2APY}
symbol={userReserve.reserve.symbol}
incentives={v2Incentives}
- variant="main14"
+ variant="subheader1"
color={baseColor}
market={currentMarket}
/>
@@ -183,7 +183,7 @@ export const MigrationListItem = ({
value={v3APY}
symbol={userReserve.reserve.symbol}
incentives={v3Incentives}
- variant="main14"
+ variant="subheader1"
color={baseColor}
market={currentMarket}
/>
@@ -197,7 +197,7 @@ export const MigrationListItem = ({
userReserve.reserve.reserveLiquidationThreshold !== '0' ? (
) : (
-
+
)}
@@ -215,7 +215,7 @@ export const MigrationListItem = ({
enabledAsCollateral={enabledAsCollateral}
/>
) : !enabledAsCollateral ? (
-
+
) : isIsolated ? (
-
+
) : (
@@ -247,7 +247,7 @@ export const MigrationListItem = ({
@@ -258,12 +258,7 @@ export const MigrationListItem = ({
}
/>
-
+
))}
@@ -272,9 +267,9 @@ export const MigrationListItem = ({
@@ -290,9 +285,9 @@ export const MigrationListItem = ({
/>
@@ -306,7 +301,7 @@ export const MigrationListItem = ({
{!isSupplyList &&
(loadingRates ? (
-
+
) : (
@@ -314,7 +309,7 @@ export const MigrationListItem = ({
@@ -328,7 +323,7 @@ export const MigrationListItem = ({
@@ -336,10 +331,10 @@ export const MigrationListItem = ({
))}
-
+ {
const theme = useTheme();
- const isMobile = useMediaQuery(theme.breakpoints.down(1125));
+ const isMobile = useMediaQuery(theme.breakpoints.down(TABLE_CARDS_BELOW));
if (isMobile) {
return ;
@@ -12,13 +13,13 @@ export const MigrationListItemLoader = () => {
return (
-
+
-
+
-
+
@@ -55,12 +56,12 @@ const MigrationListItemLoaderMobile = () => {
pt: 2.5,
}}
>
-
+
-
+
-
+
diff --git a/src/modules/migration/MigrationListMobileItem.tsx b/src/modules/migration/MigrationListMobileItem.tsx
index 685d7193f5..66a12f094f 100644
--- a/src/modules/migration/MigrationListMobileItem.tsx
+++ b/src/modules/migration/MigrationListMobileItem.tsx
@@ -16,6 +16,7 @@ import { TokenIcon } from 'src/components/primitives/TokenIcon';
import { ComputedUserReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useRootStore } from 'src/store/root';
import { MigrationDisabled, V3Rates } from 'src/store/v3MigrationSelectors';
+import { figVars } from 'src/utils/figmaColors';
import { useShallow } from 'zustand/shallow';
import { MigrationListItemToggler } from './MigrationListItemToggler';
@@ -70,8 +71,8 @@ export const MigrationListMobileItem = ({
useShallow((store) => [store.currentMarket, store.currentMarketData])
);
const theme = useTheme();
- const baseColorSecondary = disabled === undefined ? 'text.secondary' : 'text.muted';
- const baseColorPrimary = disabled === undefined ? 'text.primary' : 'text.muted';
+ const baseColorSecondary = disabled === undefined ? 'fg-2' : 'fg-3';
+ const baseColorPrimary = disabled === undefined ? 'fg-1' : 'fg-3';
const loadingRates = v3Rates?.ltv === undefined && v3Rates?.liquidationThreshold === undefined;
@@ -86,20 +87,18 @@ export const MigrationListMobileItem = ({
pt: 2.5,
}}
>
-
+ ({
+ sx={{
border: `2px solid ${
- disabled !== undefined
- ? theme.palette.action.disabled
- : theme.palette.text.secondary
+ disabled !== undefined ? figVars['disabled-fg'] : figVars['fg-2']
}`,
background:
disabled !== undefined
- ? theme.palette.background.disabled
+ ? figVars['bg-6']
: checked
- ? theme.palette.text.secondary
- : theme.palette.background.paper,
+ ? figVars['fg-2']
+ : figVars['surface-elevated'],
width: 16,
height: 16,
borderRadius: '2px',
@@ -109,18 +108,18 @@ export const MigrationListMobileItem = ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- })}
+ }}
onClick={disabled !== undefined ? undefined : onCheckboxClick}
>
{disabled === undefined && (
-
+
)}
-
+
@@ -148,11 +147,11 @@ export const MigrationListMobileItem = ({
-
+
@@ -184,7 +183,7 @@ export const MigrationListMobileItem = ({
@@ -192,7 +191,7 @@ export const MigrationListMobileItem = ({
value={v3APY}
symbol={userReserve.reserve.symbol}
incentives={v3Incentives}
- variant="main14"
+ variant="subheader1"
color={baseColorPrimary}
market={currentMarket}
/>
@@ -216,14 +215,14 @@ export const MigrationListMobileItem = ({
userReserve.reserve.reserveLiquidationThreshold !== '0' ? (
) : (
-
+
)}
@@ -234,7 +233,7 @@ export const MigrationListMobileItem = ({
enabledAsCollateral={enabledAsCollateral}
/>
) : !enabledAsCollateral ? (
-
+
) : isIsolated ? (
@@ -282,14 +281,14 @@ export const MigrationListMobileItem = ({
@@ -314,13 +313,13 @@ export const MigrationListMobileItem = ({
{loadingRates ? (
-
+
) : (
<>
@@ -328,7 +327,7 @@ export const MigrationListMobileItem = ({
fontSize="14px"
color={
disabled === undefined
- ? theme.palette.text.secondary
+ ? theme.palette.text.primary
: theme.palette.text.muted
}
/>
@@ -336,7 +335,7 @@ export const MigrationListMobileItem = ({
>
@@ -359,13 +358,13 @@ export const MigrationListMobileItem = ({
{loadingRates ? (
-
+
) : (
<>
@@ -373,7 +372,7 @@ export const MigrationListMobileItem = ({
fontSize="14px"
color={
disabled === undefined
- ? theme.palette.text.secondary
+ ? theme.palette.text.primary
: theme.palette.text.muted
}
/>
@@ -381,7 +380,7 @@ export const MigrationListMobileItem = ({
>
diff --git a/src/modules/migration/MigrationLists.tsx b/src/modules/migration/MigrationLists.tsx
index 419c33f6dc..4f4ab5793f 100644
--- a/src/modules/migration/MigrationLists.tsx
+++ b/src/modules/migration/MigrationLists.tsx
@@ -57,6 +57,7 @@ export const MigrationLists = ({
return (
= ({
sx={{
padding: '12px 16px 16px 16px',
border: 1,
- borderColor: 'divider',
+ borderColor: 'border-2',
borderRadius: 3,
width: '100%',
}}
>
-
+
From
@@ -107,7 +108,7 @@ export const MigrationMarketCard: FC = ({
{selectableMarkets.map((selectableMarket) => (
-
+
{selectableMarket.title}
@@ -130,7 +131,7 @@ export const MigrationMarketCard: FC = ({
>
-
+
{`${market.marketTitle}${market.isFork ? ' Fork' : ''}`}
@@ -152,7 +153,7 @@ export const MigrationMarketCard: FC = ({
) : (
)}
-
+
{!loading && userSummaryAfterMigration ? (
diff --git a/src/modules/migration/MigrationMobileList.tsx b/src/modules/migration/MigrationMobileList.tsx
index 9b5aedeb50..7c35dcb7f5 100644
--- a/src/modules/migration/MigrationMobileList.tsx
+++ b/src/modules/migration/MigrationMobileList.tsx
@@ -4,7 +4,9 @@ import { ReactNode } from 'react';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
import { ListWrapper } from 'src/components/lists/ListWrapper';
+import { IsolatedReserve } from 'src/store/v3MigrationSelectors';
+import { MigrationIsolationWarning } from './MigrationIsolationWarning';
import { MigrationSelectionBox } from './MigrationSelectionBox';
interface MigrationMobileListProps {
@@ -18,6 +20,7 @@ interface MigrationMobileListProps {
numSelected: number;
numAvailable: number;
disabled: boolean;
+ isolatedReserveV3?: IsolatedReserve;
}
export const MigrationMobileList = ({
@@ -30,19 +33,27 @@ export const MigrationMobileList = ({
numSelected,
numAvailable,
disabled,
+ isolatedReserveV3,
}: MigrationMobileListProps) => {
return (
- {titleComponent}
-
+
+
+ {titleComponent}
+
+
+
}
>
{(isAvailable || loading) && (
-
+
-
+
{numSelected}/{numAvailable} assets selected
diff --git a/src/modules/migration/MigrationSelectionBox.tsx b/src/modules/migration/MigrationSelectionBox.tsx
index c5508a75e0..8196cdb8f1 100644
--- a/src/modules/migration/MigrationSelectionBox.tsx
+++ b/src/modules/migration/MigrationSelectionBox.tsx
@@ -1,6 +1,7 @@
import { CheckIcon, MinusSmIcon } from '@heroicons/react/solid';
-import { Box, SvgIcon, useTheme } from '@mui/material';
+import { Box, SvgIcon } from '@mui/material';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
+import { figVars } from 'src/utils/figmaColors';
interface MigrationSelectionBoxProps {
allSelected: boolean;
@@ -15,10 +16,9 @@ export const MigrationSelectionBox = ({
onSelectAllClick,
disabled,
}: MigrationSelectionBoxProps) => {
- const theme = useTheme();
const selectionBoxStyle = {
- border: `2px solid ${theme.palette.text.secondary}`,
- background: theme.palette.text.secondary,
+ border: `2px solid ${figVars['fg-2']}`,
+ background: figVars['fg-2'],
width: 16,
height: 16,
borderRadius: '2px',
@@ -34,8 +34,8 @@ export const MigrationSelectionBox = ({
{allSelected ? (
-
+
) : numSelected !== 0 ? (
-
+
diff --git a/src/modules/migration/MigrationTopPanel.tsx b/src/modules/migration/MigrationTopPanel.tsx
index cc216f3117..d297fc8477 100644
--- a/src/modules/migration/MigrationTopPanel.tsx
+++ b/src/modules/migration/MigrationTopPanel.tsx
@@ -25,7 +25,7 @@ export const MigrationTopPanel = () => {
}}
>
{
if (!v3Price) return { v3Amount: undefined, v3TotalPrice: undefined };
@@ -33,34 +32,33 @@ export const StETHMigrationWarning: React.FC = ({
);
return (
-
-
-
- stETH tokens will be migrated to Wrapped stETH using Lido Protocol wrapper which leads to
- supply balance change after migration:{' '}
- {v3Amount ? (
- <>
-
- {' ('}
-
- {').'}
- >
- ) : (
-
- )}
- {' '}
-
-
+
+ stETH tokens will be migrated to Wrapped stETH using Lido Protocol wrapper which leads to
+ supply balance change after migration:{' '}
+ {v3Amount ? (
+ <>
+
+ {' ('}
+
+ {').'}
+ >
+ ) : (
+
+ )}
+ {' '}
+
);
};
diff --git a/src/modules/reserve-overview/AddTokenDropdown.tsx b/src/modules/reserve-overview/AddTokenDropdown.tsx
index 6bef75cc01..900566d453 100644
--- a/src/modules/reserve-overview/AddTokenDropdown.tsx
+++ b/src/modules/reserve-overview/AddTokenDropdown.tsx
@@ -1,19 +1,20 @@
import { Trans } from '@lingui/macro';
-import { Box, Menu, MenuItem, Typography } from '@mui/material';
+import { Box, Divider, Menu, MenuItem } from '@mui/material';
import * as React from 'react';
import { useEffect, useState } from 'react';
-import { CircleIcon } from 'src/components/CircleIcon';
-import { WalletIcon } from 'src/components/icons/WalletIcon';
-import { Base64Token, TokenIcon } from 'src/components/primitives/TokenIcon';
+import { WalletOutlineIcon } from 'src/components/icons/WalletOutlineIcon';
+import { Base64Token } from 'src/components/primitives/TokenIcon';
import { ReserveWithId } from 'src/hooks/app-data-provider/useAppDataProvider';
import { ERC20TokenType } from 'src/libs/web3-data-provider/Web3Provider';
import { useRootStore } from 'src/store/root';
import { RESERVE_DETAILS } from 'src/utils/events';
+import { ReserveHeaderIconButton } from './ReserveHeaderIconButton';
+import { MenuSectionLabel, TokenMenuItemContent } from './TokenMenuItems';
+
interface AddTokenDropdownProps {
poolReserve: ReserveWithId;
iconSymbol?: string;
- downToSM: boolean;
switchNetwork: (chainId: number) => Promise;
addERC20Token: (args: ERC20TokenType) => Promise;
currentChainId: number;
@@ -26,7 +27,6 @@ interface AddTokenDropdownProps {
export const AddTokenDropdown = ({
poolReserve,
iconSymbol,
- downToSM,
switchNetwork,
addERC20Token,
currentChainId,
@@ -81,9 +81,11 @@ export const AddTokenDropdown = ({
return (
<>
- {/* Load base64 token symbol for adding underlying and aTokens to wallet */}
+ {/* Hidden base64 image-generators for the add-to-wallet menu (they serialize the token SVG
+ for MetaMask). Absolutely positioned so these 0×0 nodes don't sit in the flex row as
+ gap-consuming siblings between the two header icon buttons. */}
{poolReserve?.underlyingToken.symbol && !/_/.test(poolReserve.underlyingToken.symbol) && (
- <>
+
)}
{isSGHO && }
- >
+
)}
-
-
- {
- trackEvent(RESERVE_DETAILS.ADD_TOKEN_TO_WALLET_DROPDOWN, {
- asset: poolReserve.underlyingToken.address,
- assetName: poolReserve.underlyingToken.name,
- });
- }}
- sx={{
- display: 'inline-flex',
- alignItems: 'center',
- '&:hover': {
- '.Wallet__icon': { opacity: '0 !important' },
- '.Wallet__iconHover': { opacity: '1 !important' },
- },
- cursor: 'pointer',
- }}
- >
-
-
-
+ ) => {
+ trackEvent(RESERVE_DETAILS.ADD_TOKEN_TO_WALLET_DROPDOWN, {
+ asset: poolReserve.underlyingToken.address,
+ assetName: poolReserve.underlyingToken.name,
+ });
+ handleClick(event);
+ }}
+ >
+
+
+
-
-
- Underlying token
-
-
+
+ Underlying token
+
{!hideAToken && (
-
-
-
- Aave aToken
-
-
+ <>
+
+
+ Aave aToken
+
-
+ >
)}
{isSGHO && sGHOTokenAddress && (
-
-
-
- Savings GHO token
-
-
+ <>
+
+
+ Savings GHO token
+
-
+ >
)}
>
diff --git a/src/modules/reserve-overview/BorrowInfo.tsx b/src/modules/reserve-overview/BorrowInfo.tsx
index 6855b3d65e..32935299f9 100644
--- a/src/modules/reserve-overview/BorrowInfo.tsx
+++ b/src/modules/reserve-overview/BorrowInfo.tsx
@@ -14,6 +14,7 @@ import { TextWithTooltip } from 'src/components/TextWithTooltip';
import { ReserveWithId } from 'src/hooks/app-data-provider/useAppDataProvider';
import { AssetCapHookData } from 'src/hooks/useAssetCapsSDK';
import { GENERAL } from 'src/utils/events';
+import { figVars } from 'src/utils/figmaColors';
import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities';
import { MarketDataType, NetworkConfig } from 'src/utils/marketsAndNetworksConfig';
@@ -75,15 +76,16 @@ export const BorrowInfo = ({
<>
Maximum amount available to borrow is{' '}
- {' '}
+ {' '}
{reserve.underlyingToken.symbol} (
).
@@ -122,26 +124,23 @@ export const BorrowInfo = ({
}
>
-
+ of
-
+ of
@@ -159,7 +158,7 @@ export const BorrowInfo = ({
}
>
-
+
)}
@@ -189,7 +188,7 @@ export const BorrowInfo = ({
incentives={borrowProtocolIncentives}
address={reserve.vToken.address}
symbol={reserve.underlyingToken.symbol}
- variant="main16"
+ variant="h4"
market={currentMarketData.market}
protocolAction={ProtocolAction.borrow}
inlineIncentives={true}
@@ -198,7 +197,7 @@ export const BorrowInfo = ({
{reserve.borrowInfo?.borrowCap.usd && reserve.borrowInfo?.borrowCap.usd !== '0' && (
Borrow cap}>
-
+
)}
diff --git a/src/modules/reserve-overview/Gho/GhoReserveConfiguration.tsx b/src/modules/reserve-overview/Gho/GhoReserveConfiguration.tsx
index 82f496e676..43bf560df8 100644
--- a/src/modules/reserve-overview/Gho/GhoReserveConfiguration.tsx
+++ b/src/modules/reserve-overview/Gho/GhoReserveConfiguration.tsx
@@ -1,7 +1,6 @@
-import { ExternalLinkIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Box, Button, Divider, SvgIcon, Typography } from '@mui/material';
-import { Link } from 'src/components/primitives/Link';
+import { Box, Divider, Typography } from '@mui/material';
+import { ExternalLinkButton } from 'src/components/ExternalLinkButton';
import { ReserveWithId } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useAssetCapsSDK } from 'src/hooks/useAssetCapsSDK';
import { useRootStore } from 'src/store/root';
@@ -31,7 +30,7 @@ export const GhoReserveConfiguration: React.FC = (
About GHO
-
+
GHO is a native decentralized, collateral-backed digital asset pegged to USD. It is
created by users via borrowing against multiple collateral. When user repays their GHO
@@ -39,55 +38,16 @@ export const GhoReserveConfiguration: React.FC = (
accrued by minters of GHO would be directly transferred to the AaveDAO treasury.
-
-
-
- Techpaper
-
-
-
-
-
-
-
- Website
-
-
-
-
-
-
-
- FAQ
-
-
-
-
-
+
+
+ Techpaper
+
+
+ Website
+
+
+ FAQ
+
diff --git a/src/modules/reserve-overview/Gho/GhoReserveTopDetails.tsx b/src/modules/reserve-overview/Gho/GhoReserveTopDetails.tsx
index 1178591e83..7890ad7abf 100644
--- a/src/modules/reserve-overview/Gho/GhoReserveTopDetails.tsx
+++ b/src/modules/reserve-overview/Gho/GhoReserveTopDetails.tsx
@@ -1,19 +1,13 @@
import { valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, useMediaQuery, useTheme } from '@mui/material';
import { BigNumber } from 'bignumber.js';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
-import { TopInfoPanelItem } from 'src/components/TopInfoPanel/TopInfoPanelItem';
import { ReserveWithId, useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
export const GhoReserveTopDetails = ({ reserve }: { reserve: ReserveWithId }) => {
const { loading } = useAppDataContext();
- const theme = useTheme();
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
-
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
const totalBorrowed = BigNumber.min(
valueToBigNumber(reserve.borrowInfo?.total.amount.value ?? '0'),
@@ -22,33 +16,24 @@ export const GhoReserveTopDetails = ({ reserve }: { reserve: ReserveWithId }) =>
return (
<>
- Total borrowed} loading={loading} hideIcon>
-
-
+ Total borrowed} loading={loading}>
+
+
- Maximum available to borrow}
- loading={loading}
- hideIcon
- >
+ Maximum available to borrow} loading={loading}>
-
+
- Price}>
+ Price}
+ sx={{ lineHeight: '0.875rem', letterSpacing: 0 }}
+ >
The Aave Protocol is programmed to always use the price of 1 GHO = $1. This is
different from using market pricing via oracles for other crypto assets. This creates
@@ -57,18 +42,9 @@ export const GhoReserveTopDetails = ({ reserve }: { reserve: ReserveWithId }) =>
}
loading={loading}
- hideIcon
>
-
-
-
-
+
+
>
);
};
diff --git a/src/modules/reserve-overview/Gho/SavingsGho.tsx b/src/modules/reserve-overview/Gho/SavingsGho.tsx
index 22adb4ba23..062fe92eee 100644
--- a/src/modules/reserve-overview/Gho/SavingsGho.tsx
+++ b/src/modules/reserve-overview/Gho/SavingsGho.tsx
@@ -86,11 +86,7 @@ export const SavingsGho = () => {
{stakeDataLoading && }
{!stakeDataLoading && stakeData && (
-
+
{' ('}
{
}
bottomLineComponent={
-
+ Instant
}
@@ -150,15 +146,15 @@ export const SavingsGho = () => {
pt: 2,
}}
>
-
+ Amount in cooldown
@@ -178,7 +174,7 @@ export const SavingsGho = () => {
Deposit
{stakeUserData.stakeTokenUserBalance !== '0' && (
- openSavingsGhoWithdraw()}>
+ openSavingsGhoWithdraw()}>
Withdraw
)}
diff --git a/src/modules/reserve-overview/ReserveActions.tsx b/src/modules/reserve-overview/ReserveActions.tsx
index d6ee4d9caa..938018c460 100644
--- a/src/modules/reserve-overview/ReserveActions.tsx
+++ b/src/modules/reserve-overview/ReserveActions.tsx
@@ -1,12 +1,11 @@
import { API_ETH_MOCK_ADDRESS } from '@aave/contract-helpers';
import { BigNumberValue, USD_DECIMALS, valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, Button, Divider, Paper, Skeleton, Stack, Typography, useTheme } from '@mui/material';
+import { Alert, Box, Button, Divider, Paper, Skeleton, Stack, Typography } from '@mui/material';
import React, { ReactNode, useState } from 'react';
import { WalletIcon } from 'src/components/icons/WalletIcon';
import { getMarketInfoById } from 'src/components/MarketSwitcher';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
-import { Warning } from 'src/components/primitives/Warning';
import { StyledTxModalToggleButton } from 'src/components/StyledToggleButton';
import { StyledTxModalToggleGroup } from 'src/components/StyledToggleButtonGroup';
import { FunSupplyButton } from 'src/components/transactions/FunCheckout/FunSupplyButton';
@@ -20,7 +19,9 @@ import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { BuyWithFiat } from 'src/modules/staking/BuyWithFiat';
import { useRootStore } from 'src/store/root';
+import { cardPaddingSx } from 'src/utils/cardStyles';
import { GENERAL } from 'src/utils/events';
+import { figVars } from 'src/utils/figmaColors';
import {
assetCanBeBorrowedByUser,
getMaxAmountAvailableToBorrow,
@@ -185,20 +186,20 @@ export const ReserveActions = ({ reserve }: ReserveActionsProps) => {
const PauseWarning = () => {
return (
-
+ Because this asset is paused, no actions can be taken until further notice
-
+
);
};
const FrozenWarning = () => {
return (
-
+
Since this asset is frozen, the only available actions are withdraw and repay which can be
accessed from the Dashboard
-
+
);
};
@@ -243,7 +244,7 @@ const ActionsSkeleton = () => {
const PaperWrapper = ({ children }: { children: ReactNode }) => {
return (
-
+ Your info
@@ -255,12 +256,12 @@ const PaperWrapper = ({ children }: { children: ReactNode }) => {
const ConnectWallet = () => {
return (
-
+
<>
Your info
-
+ Please connect a wallet to view your personal information here.
@@ -316,8 +317,8 @@ const SupplyAction = ({
@@ -375,8 +376,8 @@ const BorrowAction = ({
@@ -415,11 +416,11 @@ const WrappedBaseAssetSelector = ({
sx={{ mb: 4 }}
>
- {assetSymbol}
+ {assetSymbol}
- {baseAssetSymbol}
+ {baseAssetSymbol}
);
@@ -434,8 +435,8 @@ interface ValueWithSymbolProps {
const ValueWithSymbol = ({ value, symbol, children }: ValueWithSymbolProps) => {
return (
-
-
+
+
{symbol}
{children}
@@ -449,26 +450,24 @@ interface WalletBalanceProps {
marketTitle: string;
}
export const WalletBalance = ({ balance, symbol, marketTitle }: WalletBalanceProps) => {
- const theme = useTheme();
-
return (
({
+ sx={{
width: '42px',
height: '42px',
- background: theme.palette.background.surface,
- border: `0.5px solid ${theme.palette.background.disabled}`,
+ background: figVars['bg-2'],
+ border: `0.5px solid ${figVars['bg-6']}`,
borderRadius: '12px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- })}
+ }}
>
-
+
-
+
Wallet balance
diff --git a/src/modules/reserve-overview/ReserveConfiguration.tsx b/src/modules/reserve-overview/ReserveConfiguration.tsx
index b56dbd095e..76b18d4fb8 100644
--- a/src/modules/reserve-overview/ReserveConfiguration.tsx
+++ b/src/modules/reserve-overview/ReserveConfiguration.tsx
@@ -1,12 +1,11 @@
import { AaveV2Ethereum } from '@aave-dao/aave-address-book';
import { ExternalLinkIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Box, Button, Divider, SvgIcon } from '@mui/material';
+import { Alert, Box, Button, Divider, SvgIcon } from '@mui/material';
import { getFrozenProposalLink } from 'src/components/infoTooltips/FrozenTooltip';
import { PausedTooltipText } from 'src/components/infoTooltips/PausedTooltip';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Link } from 'src/components/primitives/Link';
-import { Warning } from 'src/components/primitives/Warning';
import { AMPLWarning } from 'src/components/Warnings/AMPLWarning';
import { BorrowDisabledWarning } from 'src/components/Warnings/BorrowDisabledWarning';
import {
@@ -62,7 +61,7 @@ export const ReserveConfiguration: React.FC = ({ rese
<>
{reserve.isFrozen && !offboardingDiscussion ? (
-
+
This asset is frozen due to an Aave community decision.{' '}
= ({ rese
More details
-
+
) : offboardingDiscussion ? (
-
+
-
+
) : (
reserve.underlyingToken.symbol == 'AMPL' && (
-
+
-
+
)
)}
{reserve.isPaused ? (
reserve.underlyingToken.symbol === 'MAI' ? (
-
+
MAI has been paused due to a community decision. Supply, borrows and repays are
impacted.{' '}
@@ -103,11 +102,11 @@ export const ReserveConfiguration: React.FC = ({ rese
More details
-
+
) : (
-
+
-
+
)
) : null}
@@ -134,12 +133,12 @@ export const ReserveConfiguration: React.FC = ({ rese
{reserve.borrowInfo?.borrowingState !== 'ENABLED' &&
!reserve.eModeInfo?.some((eMode) => eMode.canBeBorrowed) && (
-
+
-
+
)}
= ({ rese
@@ -204,7 +203,7 @@ export const ReserveConfiguration: React.FC = ({ rese
}
component={Link}
size="small"
- variant="outlined"
+ variant="tertiary"
sx={{ verticalAlign: 'top' }}
>
Interest rate strategy
diff --git a/src/modules/reserve-overview/ReserveConfigurationWrapper.tsx b/src/modules/reserve-overview/ReserveConfigurationWrapper.tsx
index 0a0a009e19..39d216019d 100644
--- a/src/modules/reserve-overview/ReserveConfigurationWrapper.tsx
+++ b/src/modules/reserve-overview/ReserveConfigurationWrapper.tsx
@@ -1,8 +1,9 @@
import { Trans } from '@lingui/macro';
-import { Box, Paper, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Box, Paper, Typography } from '@mui/material';
import dynamic from 'next/dynamic';
import { ReserveWithId } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useRootStore } from 'src/store/root';
+import { cardPaddingSx } from 'src/utils/cardStyles';
import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities';
type ReserveConfigurationProps = {
@@ -19,15 +20,13 @@ const ReserveConfiguration = dynamic(() =>
export const ReserveConfigurationWrapper: React.FC = ({ reserve }) => {
const currentMarket = useRootStore((state) => state.currentMarket);
- const { breakpoints } = useTheme();
- const downToXsm = useMediaQuery(breakpoints.down('xsm'));
const isGho = displayGhoForMintableMarket({
symbol: reserve.underlyingToken.symbol,
currentMarket,
});
return (
-
+ = ({ reserve }
@@ -73,7 +73,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
@@ -88,7 +88,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
@@ -96,7 +96,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
))}
-
+
E-Mode increases your LTV for a selected category of assets, meaning that when E-mode is
enabled, you will have higher borrowing power over assets of the same E-mode category
@@ -105,7 +105,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
href={ROUTES.dashboard}
sx={{ textDecoration: 'underline' }}
variant="caption"
- color="text.secondary"
+ color="fg-2"
onClick={() => {
trackEvent(RESERVE_DETAILS.GO_DASHBOARD_EMODE);
}}
@@ -117,7 +117,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
href="https://aave.com/help/borrowing/e-mode"
sx={{ textDecoration: 'underline' }}
variant="caption"
- color="text.secondary"
+ color="fg-2"
onClick={() => {
trackEvent(GENERAL.EXTERNAL_LINK, { Link: 'E-mode FAQ' });
}}
@@ -129,7 +129,7 @@ export const ReserveEModePanel: React.FC = ({ reserve }
href="https://github.com/aave/aave-v3-core/blob/master/techpaper/Aave_V3_Technical_Paper.pdf"
sx={{ textDecoration: 'underline' }}
variant="caption"
- color="text.secondary"
+ color="fg-2"
onClick={() => {
trackEvent(GENERAL.EXTERNAL_LINK, { Link: 'V3 Tech Paper' });
}}
@@ -170,14 +170,14 @@ export const ConfigStatus = ({
) : enabled ? (
-
+
) : (
-
+
)}
{label && (
{label}
diff --git a/src/modules/reserve-overview/ReserveFactorOverview.tsx b/src/modules/reserve-overview/ReserveFactorOverview.tsx
index 9937620893..cedfa8559a 100644
--- a/src/modules/reserve-overview/ReserveFactorOverview.tsx
+++ b/src/modules/reserve-overview/ReserveFactorOverview.tsx
@@ -58,7 +58,7 @@ export const ReserveFactorOverview = ({
/>
}
>
-
+
-
+ View contract
diff --git a/src/modules/reserve-overview/ReserveHeaderIconButton.tsx b/src/modules/reserve-overview/ReserveHeaderIconButton.tsx
new file mode 100644
index 0000000000..5e32c654f4
--- /dev/null
+++ b/src/modules/reserve-overview/ReserveHeaderIconButton.tsx
@@ -0,0 +1,53 @@
+import { Trans } from '@lingui/macro';
+import { Box, Typography } from '@mui/material';
+import { ReactNode } from 'react';
+import { DarkTooltip } from 'src/components/infoTooltips/DarkTooltip';
+import { figSurfaceShadow } from 'src/utils/figmaColors';
+
+interface ReserveHeaderIconButtonProps {
+ tooltipText: string;
+ /** Button diameter — 1.75rem next to the token name, 1.25rem beside the oracle price. */
+ size?: string;
+ children: ReactNode;
+}
+
+// Surface icon button for the reserve header affordances (token contracts / add-to-wallet /
+// oracle link): a bg-3 circle with the shared shadow-low-border-2 ring. The icon color is a
+// constant `fg-2` via `currentColor` (icon children only need `stroke="currentColor"`); hover
+// tints the circle background instead — one step down the ramp to bg-5.
+export const ReserveHeaderIconButton = ({
+ tooltipText,
+ size = '1.75rem',
+ children,
+}: ReserveHeaderIconButtonProps) => {
+ return (
+
+ {tooltipText}
+
+ }
+ >
+
+ {children}
+
+
+ );
+};
diff --git a/src/modules/reserve-overview/ReservePanels.tsx b/src/modules/reserve-overview/ReservePanels.tsx
index 70f359669e..57590e363d 100644
--- a/src/modules/reserve-overview/ReservePanels.tsx
+++ b/src/modules/reserve-overview/ReservePanels.tsx
@@ -1,5 +1,6 @@
import { Box, BoxProps, Typography, TypographyProps, useMediaQuery, useTheme } from '@mui/material';
import type { ReactNode } from 'react';
+import { figVars } from 'src/utils/figmaColors';
export const PanelRow: React.FC = (props) => (
= ({ title, children, className
position: 'absolute',
right: 4,
top: 'calc(50% - 17px)',
- borderRight: (theme) => `1px solid ${theme.palette.divider}`,
+ borderRight: `1px solid ${figVars['border-2']}`,
},
}
: {}),
}}
className={className}
>
-
+
{title}
[store.trackEvent, store.currentNetworkConfig])
);
- const theme = useTheme();
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
-
const poolReserve = reserves.find(
(reserve) => reserve.underlyingAsset === underlyingAsset
) as ComputedReserveData;
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
-
- const iconStyling = {
- display: 'inline-flex',
- alignItems: 'center',
- color: '#A5A8B6',
- '&:hover': { color: '#F1F1F3' },
- cursor: 'pointer',
- };
-
return (
<>
- Reserve Size} loading={loading} hideIcon>
+ Reserve size} loading={loading}>
-
+
- Available liquidity} loading={loading} hideIcon>
+ Available liquidity} loading={loading}>
-
+
- Utilization Rate} loading={loading} hideIcon>
-
-
+ Utilization rate} loading={loading}>
+
+
- Oracle price} loading={loading} hideIcon>
-
-
- {loading ? (
-
- ) : (
-
-
- trackEvent(GENERAL.EXTERNAL_LINK, {
- Link: 'Oracle Price',
- oracle: poolReserve?.priceOracle,
- assetName: poolReserve.name,
- asset: poolReserve.underlyingAsset,
- })
- }
- href={currentNetworkConfig.explorerLinkBuilder({
- address: poolReserve?.priceOracle,
- })}
- sx={iconStyling}
- >
-
-
-
-
-
- )}
+ Oracle price} loading={loading}>
+
+
+
+
+ trackEvent(GENERAL.EXTERNAL_LINK, {
+ Link: 'Oracle Price',
+ oracle: poolReserve?.priceOracle,
+ assetName: poolReserve.name,
+ asset: poolReserve.underlyingAsset,
+ })
+ }
+ href={currentNetworkConfig.explorerLinkBuilder({
+ address: poolReserve?.priceOracle,
+ })}
+ sx={{
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ height: '100%',
+ color: 'inherit',
+ }}
+ >
+
+
+
-
+
>
);
};
diff --git a/src/modules/reserve-overview/ReserveTopDetailsWrapper.tsx b/src/modules/reserve-overview/ReserveTopDetailsWrapper.tsx
index d627000594..ecd12842e6 100644
--- a/src/modules/reserve-overview/ReserveTopDetailsWrapper.tsx
+++ b/src/modules/reserve-overview/ReserveTopDetailsWrapper.tsx
@@ -1,16 +1,7 @@
import { Trans } from '@lingui/macro';
-import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackOutlined';
-import {
- Box,
- Button,
- Divider,
- Skeleton,
- SvgIcon,
- Typography,
- useMediaQuery,
- useTheme,
-} from '@mui/material';
+import { Box, Skeleton, SvgIcon, Typography } from '@mui/material';
import { useRouter } from 'next/router';
+import { DarkTooltip } from 'src/components/infoTooltips/DarkTooltip';
import { getMarketInfoById, MarketLogo } from 'src/components/MarketSwitcher';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
@@ -19,7 +10,6 @@ import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities';
import { useShallow } from 'zustand/shallow';
import { TopInfoPanel } from '../../components/TopInfoPanel/TopInfoPanel';
-import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
import { useAppDataContext } from '../../hooks/app-data-provider/useAppDataProvider';
import { AddTokenDropdown } from './AddTokenDropdown';
import { GhoReserveTopDetails } from './Gho/GhoReserveTopDetails';
@@ -36,8 +26,6 @@ export const ReserveTopDetailsWrapper = ({ underlyingAsset }: ReserveTopDetailsP
const [currentMarket, currentChainId] = useRootStore(
useShallow((state) => [state.currentMarket, state.currentChainId])
);
-
- const { market, logo } = getMarketInfoById(currentMarket);
const {
addERC20Token,
switchNetwork,
@@ -45,8 +33,7 @@ export const ReserveTopDetailsWrapper = ({ underlyingAsset }: ReserveTopDetailsP
currentAccount,
} = useWeb3Context();
- const theme = useTheme();
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
+ const { market, logo } = getMarketInfoById(currentMarket);
const poolReserve = supplyReserves.find(
(reserve) => reserve.underlyingToken.address.toLowerCase() === underlyingAsset?.toLowerCase()
@@ -65,32 +52,31 @@ export const ReserveTopDetailsWrapper = ({ underlyingAsset }: ReserveTopDetailsP
? iconSymbol
: poolReserve!.underlyingToken.symbol;
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
-
- const ReserveIcon = () => {
- return (
-
- {loading ? (
-
- ) : (
-
- )}
-
- );
- };
+ const reserveIcon = (
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+ );
- const ReserveName = () => {
- return loading ? (
-
- ) : (
- {poolReserve.underlyingToken.name}
- );
- };
+ const reserveName = loading ? (
+
+ ) : (
+
+ {poolReserve.underlyingToken.name}}>
+
+ {poolReserve.underlyingToken.name}
+
+
+
+ );
const isGho = displayGhoForMintableMarket({
symbol: poolReserve.underlyingToken.symbol,
@@ -100,144 +86,122 @@ export const ReserveTopDetailsWrapper = ({ underlyingAsset }: ReserveTopDetailsP
return (
- {
+ // https://github.com/vercel/next.js/discussions/34980
+ if (!!history.state.idx) router.back();
+ else router.push('/markets');
+ }}
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.25rem',
+ width: 'fit-content',
+ mb: '1rem',
+ cursor: 'pointer',
+ color: 'fg-3',
+ '&:hover': { color: 'fg-1' },
+ }}
+ >
+
+
+
+
-
-
-
- }
- onClick={() => {
- // https://github.com/vercel/next.js/discussions/34980
- if (!!history.state.idx) router.back();
- else router.push('/markets');
- }}
- sx={{ mr: 3, mb: downToSM ? '24px' : '0' }}
- >
- Go Back
-
-
-
-
-
- {market.marketTitle} Market
-
- {market.v3 && (
- theme.palette.gradients.aaveGradient,
- }}
- >
- Version 3
-
- )}
-
-
-
- {downToSM && (
-
-
-
+ Back
+
+
+ }
+ >
+
+
+ {reserveIcon}
+
+
+
+ {reserveName}
{!loading && (
-
+
{poolReserve.underlyingToken.symbol}
)}
-
-
- {loading ? (
-
- ) : (
-
-
- {currentAccount && (
-
- )}
-
- )}
-
-
- )}
-
- }
- >
- {!downToSM && (
- <>
- {poolReserve.underlyingToken.symbol}}
- withoutIconWrapper
- icon={}
- loading={loading}
- >
-
-
-
-
-
- {currentAccount && (
-
+
- )}
-
+ {currentAccount && (
+
+ )}
+
+ )}
-
-
- >
- )}
- {isGho ? (
-
- ) : (
-
- )}
+
+
+ on
+
+
+
+ {market.marketTitle}
+
+
+
+
+
+
+ {isGho ? (
+
+ ) : (
+
+ )}
+
+
);
};
diff --git a/src/modules/reserve-overview/SupplyInfo.tsx b/src/modules/reserve-overview/SupplyInfo.tsx
index e111353d83..b20b3a775f 100644
--- a/src/modules/reserve-overview/SupplyInfo.tsx
+++ b/src/modules/reserve-overview/SupplyInfo.tsx
@@ -1,7 +1,7 @@
import { ProtocolAction } from '@aave/contract-helpers';
import { valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { AlertTitle, Box, Typography } from '@mui/material';
+import { Alert, AlertTitle, Box, Typography } from '@mui/material';
import { CapsCircularStatus } from 'src/components/caps/CapsCircularStatus';
import { DebtCeilingStatus } from 'src/components/caps/DebtCeilingStatus';
import { mapAaveProtocolIncentives } from 'src/components/incentives/incentives.helper';
@@ -11,7 +11,6 @@ import { LiquidationThresholdTooltip } from 'src/components/infoTooltips/Liquida
import { MaxLTVTooltip } from 'src/components/infoTooltips/MaxLTVTooltip';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Link } from 'src/components/primitives/Link';
-import { Warning } from 'src/components/primitives/Warning';
import { ReserveOverviewBox } from 'src/components/ReserveOverviewBox';
import { ReserveSubheader } from 'src/components/ReserveSubheader';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
@@ -67,7 +66,7 @@ export const SupplyInfo = ({
valueToBigNumber(reserve.supplyInfo.supplyCap.amount.value).toNumber() -
valueToBigNumber(reserve.supplyInfo.total.value).toNumber()
}
- variant="secondary12"
+ variant="subheader2"
/>{' '}
{reserve.underlyingToken.symbol} (
).
@@ -114,26 +113,23 @@ export const SupplyInfo = ({
}
>
-
+ of
-
+ of
@@ -151,7 +147,7 @@ export const SupplyInfo = ({
}
>
-
+
)}
@@ -161,7 +157,7 @@ export const SupplyInfo = ({
incentives={supplyProtocolIncentives}
address={reserve.aToken.address}
symbol={reserve.underlyingToken.symbol}
- variant="main16"
+ variant="h4"
market={currentMarketData.market}
protocolAction={ProtocolAction.supply}
inlineIncentives={true}
@@ -184,19 +180,16 @@ export const SupplyInfo = ({
Collateral usage
-
-
+
+ Asset can only be used as collateral in isolation mode only.
-
-
- In Isolation mode you cannot supply other assets as collateral for borrowing. Assets
- used as collateral in Isolation mode can only be borrowed to a specific debt
- ceiling.{' '}
-
- Learn more
-
-
-
+
+ In Isolation mode you cannot supply other assets as collateral for borrowing. Assets
+ used as collateral in Isolation mode can only be borrowed to a specific debt ceiling.{' '}
+
+ Learn more
+
+
) : reserve.supplyInfo.liquidationThreshold.value !== '0' ? (
Collateral usage
-
+
This asset can only be used as collateral in E-Mode:{' '}
{reserve.eModeInfo
@@ -225,16 +218,16 @@ export const SupplyInfo = ({
.map((eMode) => replaceUnderscoresWithSpaces(eMode.label))
.join(', ')}
-
+
) : (
Collateral usage
-
+ Asset cannot be used as collateral.
-
+
)}
@@ -265,7 +258,7 @@ export const SupplyInfo = ({
@@ -289,7 +282,7 @@ export const SupplyInfo = ({
@@ -313,7 +306,7 @@ export const SupplyInfo = ({
@@ -331,7 +324,7 @@ export const SupplyInfo = ({
)}
{reserve.underlyingToken.symbol == 'stETH' && (
-
+ Staking Rewards
@@ -345,7 +338,7 @@ export const SupplyInfo = ({
>
Learn more
-
+
)}
diff --git a/src/modules/reserve-overview/TimeRangeSelector.tsx b/src/modules/reserve-overview/TimeRangeSelector.tsx
index 395443be05..9bfd2f2f4b 100644
--- a/src/modules/reserve-overview/TimeRangeSelector.tsx
+++ b/src/modules/reserve-overview/TimeRangeSelector.tsx
@@ -1,5 +1,7 @@
import { TimeWindow } from '@aave/react';
-import { SxProps, Theme, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
+import { SxProps, Theme, Typography } from '@mui/material';
+import { StyledTxModalToggleButton } from 'src/components/StyledToggleButton';
+import { StyledTxModalToggleGroup } from 'src/components/StyledToggleButtonGroup';
export const supportedTimeRangeOptions = ['1m', '3m', '6m', '1y'] as const;
@@ -52,47 +54,20 @@ export const TimeRangeSelector = ({
};
return (
-
- {timeRanges.map((interval) => {
- return (
- | undefined => ({
- '&.MuiToggleButtonGroup-grouped:not(.Mui-selected), &.MuiToggleButtonGroup-grouped&.Mui-disabled':
- {
- border: '0.5px solid transparent',
- backgroundColor: 'background.surface',
- color: 'action.disabled',
- },
- '&.MuiToggleButtonGroup-grouped&.Mui-selected': {
- borderRadius: '4px',
- border: `0.5px solid ${theme.palette.divider}`,
- boxShadow: '0px 2px 1px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.25)',
- backgroundColor: 'background.paper',
- },
- ...props.sx?.button,
- })}
- >
- {formattedInterval(interval)}
-
- );
- })}
-
+ {timeRanges.map((interval) => (
+
+ {formattedInterval(interval)}
+
+ ))}
+
);
};
diff --git a/src/modules/reserve-overview/TokenLinkDropdown.tsx b/src/modules/reserve-overview/TokenLinkDropdown.tsx
index 15d71038ff..f083ce7fc1 100644
--- a/src/modules/reserve-overview/TokenLinkDropdown.tsx
+++ b/src/modules/reserve-overview/TokenLinkDropdown.tsx
@@ -1,20 +1,19 @@
-import { ExternalLinkIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import { Box, Menu, MenuItem, SvgIcon, Typography } from '@mui/material';
+import { Box, Divider, Menu, MenuItem } from '@mui/material';
import * as React from 'react';
import { useState } from 'react';
-import { CircleIcon } from 'src/components/CircleIcon';
-import { TokenIcon } from 'src/components/primitives/TokenIcon';
+import { ArrowUpRightIcon } from 'src/components/icons/ArrowUpRightIcon';
import { ReserveWithId } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useRootStore } from 'src/store/root';
import { useShallow } from 'zustand/shallow';
import { RESERVE_DETAILS } from '../../utils/events';
+import { ReserveHeaderIconButton } from './ReserveHeaderIconButton';
+import { MenuSectionLabel, TokenMenuItemContent } from './TokenMenuItems';
interface TokenLinkDropdownProps {
poolReserve: ReserveWithId;
iconSymbol?: string;
- downToSM: boolean;
hideAToken?: boolean;
hideVariableDebtToken?: boolean;
}
@@ -22,7 +21,6 @@ interface TokenLinkDropdownProps {
export const TokenLinkDropdown = ({
poolReserve,
iconSymbol,
- downToSM,
hideAToken,
hideVariableDebtToken,
}: TokenLinkDropdownProps) => {
@@ -61,21 +59,9 @@ export const TokenLinkDropdown = ({
return (
<>
-
-
-
-
-
-
-
+
+
+
-
-
- Underlying token
-
-
+
+ Underlying token
+
{!hideAToken && (
-
-
-
- Aave aToken
-
-
+ <>
+
+
+ Aave aToken
+
-
+ >
)}
{showVariableDebtToken && (
-
-
+ <>
+
+ Aave debt token
-
-
- )}
- {showVariableDebtToken && (
-
+
+
+ >
)}
>
diff --git a/src/modules/reserve-overview/TokenMenuItems.tsx b/src/modules/reserve-overview/TokenMenuItems.tsx
new file mode 100644
index 0000000000..207c934b59
--- /dev/null
+++ b/src/modules/reserve-overview/TokenMenuItems.tsx
@@ -0,0 +1,36 @@
+import { Box, ListItemIcon, ListItemText, Typography } from '@mui/material';
+import { ReactNode } from 'react';
+import { TokenIcon } from 'src/components/primitives/TokenIcon';
+
+/** Group heading inside the reserve token dropdowns; the 0.38rem inset aligns it with the rows. */
+export const MenuSectionLabel = ({ children }: { children: ReactNode }) => (
+
+
+ {children}
+
+
+);
+
+interface TokenMenuItemContentProps {
+ symbol: string;
+ label: ReactNode;
+ aToken?: boolean;
+ waToken?: boolean;
+}
+
+/** Icon + symbol row shared by the "view contracts" and "add to wallet" token dropdowns. */
+export const TokenMenuItemContent = ({
+ symbol,
+ label,
+ aToken,
+ waToken,
+}: TokenMenuItemContentProps) => (
+ <>
+
+
+
+
+ {label}
+
+ >
+);
diff --git a/src/modules/reserve-overview/graphs/ApyGraph.tsx b/src/modules/reserve-overview/graphs/ApyGraph.tsx
index 35a14591ac..86869d65a2 100644
--- a/src/modules/reserve-overview/graphs/ApyGraph.tsx
+++ b/src/modules/reserve-overview/graphs/ApyGraph.tsx
@@ -180,7 +180,7 @@ export const ApyGraph = withTooltip(
borderRadius: '99px',
}}
>
-
+
Avg {avgFormatted}%
@@ -303,11 +303,7 @@ export const ApyGraph = withTooltip(
left={tooltipLeft + 40}
style={theme.palette.mode === 'light' ? tooltipStyles : tooltipStylesDark}
>
-
+
{formatDate(getDate(tooltipData), selectedTimeRange)}
(
justifyContent="space-between"
alignItems="center"
>
-
+
{field.text}
-
+
{getData(tooltipData, field.name).toFixed(2)}%
@@ -382,7 +378,7 @@ export const PlaceholderChart = ({
-
+
No data available
diff --git a/src/modules/reserve-overview/graphs/ApyGraphContainer.tsx b/src/modules/reserve-overview/graphs/ApyGraphContainer.tsx
index 644f548bf5..19888fef1c 100644
--- a/src/modules/reserve-overview/graphs/ApyGraphContainer.tsx
+++ b/src/modules/reserve-overview/graphs/ApyGraphContainer.tsx
@@ -7,9 +7,10 @@ import {
useSupplyAPYHistory,
} from '@aave/react';
import { Trans } from '@lingui/macro';
-import { Box, CircularProgress, Typography } from '@mui/material';
+import { Box, CircularProgress, Typography, useTheme } from '@mui/material';
import { ParentSize } from '@visx/responsive';
import { useState } from 'react';
+import { pickFigma } from 'src/utils/figmaColors';
import { ApyGraph, FormattedReserveHistoryItem, PlaceholderChart } from './ApyGraph';
import { GraphLegend } from './GraphLegend';
@@ -42,6 +43,7 @@ type ApyGraphProps = {
export const SupplyApyGraph = ({ chain, underlyingToken, market }: ApyGraphProps) => {
const [selectedTimeRange, setSelectedTimeRange] = useState(TimeWindow.LastWeek);
+ const { palette } = useTheme();
const { data, loading, error } = useSupplyAPYHistory({
chainId: chainId(chain),
@@ -53,7 +55,7 @@ export const SupplyApyGraph = ({ chain, underlyingToken, market }: ApyGraphProps
return (
{
const [selectedTimeRange, setSelectedTimeRange] = useState(TimeWindow.LastWeek);
+ const { palette } = useTheme();
const { data, loading, error } = useBorrowAPYHistory({
chainId: chainId(chain),
@@ -76,7 +79,7 @@ export const BorrowApyGraph = ({ chain, underlyingToken, market }: ApyGraphProps
return (
-
+ Loading data...
diff --git a/src/modules/reserve-overview/graphs/GraphLegend.tsx b/src/modules/reserve-overview/graphs/GraphLegend.tsx
index 5a4679bd0e..7060b8765c 100644
--- a/src/modules/reserve-overview/graphs/GraphLegend.tsx
+++ b/src/modules/reserve-overview/graphs/GraphLegend.tsx
@@ -23,7 +23,7 @@ export function GraphLegend({
borderRadius: '50%',
}}
/>
-
+
{label.text}
diff --git a/src/modules/reserve-overview/graphs/InterestRateModelGraph.tsx b/src/modules/reserve-overview/graphs/InterestRateModelGraph.tsx
index e5224dccdf..8e229fafcc 100644
--- a/src/modules/reserve-overview/graphs/InterestRateModelGraph.tsx
+++ b/src/modules/reserve-overview/graphs/InterestRateModelGraph.tsx
@@ -378,7 +378,7 @@ export const InterestRateModelGraph = withTooltip(
parseFloat(reserve.totalDebtUSD) >
0 ? (
<>
-
+ Borrow amount to reach {tooltipData.utilization}% utilization
@@ -394,7 +394,7 @@ export const InterestRateModelGraph = withTooltip(
>
) : (
<>
-
+
Repayment amount to reach {tooltipData.utilization}% utilization
@@ -417,10 +417,10 @@ export const InterestRateModelGraph = withTooltip(
{fields.map((field) => (
-
+
{field.text}
-
+
{tooltipValueAccessors[field.name](tooltipData).toFixed(2)}%
diff --git a/src/modules/reserve-overview/graphs/MeritApyGraph.tsx b/src/modules/reserve-overview/graphs/MeritApyGraph.tsx
index 40d848a32c..d2ab2b03f6 100644
--- a/src/modules/reserve-overview/graphs/MeritApyGraph.tsx
+++ b/src/modules/reserve-overview/graphs/MeritApyGraph.tsx
@@ -196,7 +196,7 @@ export const MeritApyGraph = withTooltip(
borderRadius: '99px',
}}
>
-
+
Avg {averageLine.avgFormatted}%
@@ -287,18 +287,14 @@ export const MeritApyGraph = withTooltip(
left={tooltipLeft + 40}
style={theme.palette.mode === 'light' ? tooltipStyles : tooltipStylesDark}
>
-
+
{formatDate(getDate(tooltipData))}
-
+
Merit APY
-
+
{getMeritApy(tooltipData).toFixed(2)}%
diff --git a/src/modules/reserve-overview/graphs/MeritApyGraphContainer.tsx b/src/modules/reserve-overview/graphs/MeritApyGraphContainer.tsx
index 5de95b4252..2c16bf4f1a 100644
--- a/src/modules/reserve-overview/graphs/MeritApyGraphContainer.tsx
+++ b/src/modules/reserve-overview/graphs/MeritApyGraphContainer.tsx
@@ -98,7 +98,7 @@ export const MeritApyGraphContainer = ({
}}
>
-
+ Loading data...
@@ -122,7 +122,7 @@ export const MeritApyGraphContainer = ({
Data couldn't be fetched, please reload graph.
{onRetry && (
-
+ Reload
)}
diff --git a/src/modules/sGho/SGhoCard.tsx b/src/modules/sGho/SGhoCard.tsx
index 12fb435a21..25df16cbd3 100644
--- a/src/modules/sGho/SGhoCard.tsx
+++ b/src/modules/sGho/SGhoCard.tsx
@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro';
-import { Box, Paper, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Box, Paper, Typography } from '@mui/material';
import { useWalletBalances } from 'src/hooks/app-data-provider/useWalletBalances';
import { useModalContext } from 'src/hooks/useModal';
import { useSavingsMarketData } from 'src/hooks/useSavingsMarketData';
@@ -10,8 +10,6 @@ import { SGhoDepositPanel } from './SGhoDepositPanel';
export const SGhoCard = () => {
const { chainId, marketKey } = useSavingsMarketData();
- const { breakpoints } = useTheme();
- const downToXsm = useMediaQuery(breakpoints.down('xsm'));
const { openSwitch, openSGhoVaultDeposit, openSGhoVaultWithdraw } = useModalContext();
const { vault, loading: vaultLoading } = useSGhoVaultContext();
@@ -42,10 +40,9 @@ export const SGhoCard = () => {
return (
{
- const { breakpoints } = useTheme();
- const xsm = useMediaQuery(breakpoints.up('xsm'));
-
const hasGho = +walletBalance > 0;
return (
- ({
- display: 'flex',
- alignItems: { xs: 'stretch', xsm: 'center' },
- justifyContent: 'space-between',
- flexDirection: { xs: 'column', xsm: 'row' },
- gap: 4,
- borderRadius: { xs: '8px', xsm: '6px' },
- border: `1px solid ${theme.palette.divider}`,
- p: 4,
- mb: 6,
- background: theme.palette.background.paper,
- })}
- >
+
@@ -43,41 +29,32 @@ export const SGhoDepositRow = ({
sGHO
-
+ Available to deposit:
-
+
-
+ Staking APR
-
+
{hasGho ? (
Deposit
@@ -86,8 +63,7 @@ export const SGhoDepositRow = ({
Get GHO
diff --git a/src/modules/sGho/SGhoHeader.tsx b/src/modules/sGho/SGhoHeader.tsx
index ef6d450ab9..6f06a44d32 100644
--- a/src/modules/sGho/SGhoHeader.tsx
+++ b/src/modules/sGho/SGhoHeader.tsx
@@ -1,19 +1,18 @@
import { Trans } from '@lingui/macro';
-import { Box, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Typography } from '@mui/material';
import NumberFlow from '@number-flow/react';
import { BigNumber } from 'bignumber.js';
import { useEffect, useState } from 'react';
+import { PageHeader } from 'src/components/PageHeader/PageHeader';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { TokenIcon } from 'src/components/primitives/TokenIcon';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
-import { TopInfoPanel } from 'src/components/TopInfoPanel/TopInfoPanel';
import { useSGhoVaultContext } from 'src/modules/sGho/SGhoVaultContext';
import { useRootStore } from 'src/store/root';
-
-import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
+import { convertAprToApy } from 'src/utils/utils';
export const SGHOHeader: React.FC = () => {
- const theme = useTheme();
const trackEvent = useRootStore((store) => store.trackEvent);
const { vault, loading } = useSGhoVaultContext();
@@ -23,16 +22,8 @@ export const SGHOHeader: React.FC = () => {
});
}, [trackEvent]);
- const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
-
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
- const symbolsColor = theme.palette.text.muted;
- const iconSize = valueTypographyVariant === 'main21' ? 20 : 16;
-
const apr = vault?.targetRate ? +vault.targetRate.value : 0;
+ const apyPercent = (convertAprToApy(apr) * 100).toFixed(2);
const totalDepositedUSD = vault?.totalAssets?.usd ?? '0';
const totalAssetsValue = vault?.totalAssets ? +vault.totalAssets.amount.value : 0;
@@ -54,83 +45,46 @@ export const SGHOHeader: React.FC = () => {
}, [weeklyRewardsEstimate]);
return (
-
-
-
-
- Savings GHO
-
-
-
-
-
- Deposit GHO into Savings GHO (sGHO) and earn{' '}
-
- {(apr * 100).toFixed(2)}%
- {' '}
- APR on your GHO holdings. There are no lockups, no rehypothecation, and you can
- withdraw anytime. Simply deposit GHO, receive sGHO tokens representing your balance,
- and watch your savings grow.
-
-
-
+ Savings GHO}
+ titleIcon={}
+ description={
+
+ Deposit GHO into Savings GHO (sGHO) and earn {apyPercent}% APY on your GHO holdings.
+
}
>
- Current APR} loading={loading}>
-
-
+ Current APR} loading={loading}>
+
+
- Total Deposited} loading={loading}>
+ Total Deposited} loading={loading}>
-
+
- Price} loading={loading}>
-
-
+ Price} loading={loading}>
+
+
-
- Weekly Rewards} variant="inherit">
-
- Estimated weekly rewards based on your current sGHO balance and APR. Actual rewards
- may vary depending on market conditions.
-
-
-
+ Weekly Rewards} variant="inherit">
+
+ Estimated weekly rewards based on your current sGHO balance and APR. Actual rewards
+ may vary depending on market conditions.
+
+
}
loading={loading}
>
{balanceBN.gt(0) ? (
{
}}
className="custom-number-flow"
/>
-
+
) : (
-
+
—
)}
-
-
+
+
);
};
diff --git a/src/modules/sGho/SGhoLoggedOutPreview.tsx b/src/modules/sGho/SGhoLoggedOutPreview.tsx
index 8347fe2d72..cab894b85c 100644
--- a/src/modules/sGho/SGhoLoggedOutPreview.tsx
+++ b/src/modules/sGho/SGhoLoggedOutPreview.tsx
@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro';
-import { Box, Button, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { Box, Button, Typography } from '@mui/material';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
+import { figVars } from 'src/utils/figmaColors';
import { StakeActionBox } from '../staking/StakeActionBox';
@@ -15,37 +16,33 @@ interface SGhoLoggedOutPreviewProps {
* connect. Actual connect prompt lives in the sidebar.
*/
export const SGhoLoggedOutPreview = ({ rate }: SGhoLoggedOutPreviewProps) => {
- const { breakpoints } = useTheme();
- const xsm = useMediaQuery(breakpoints.up('xsm'));
-
return (
-
+ Deposit GHO
-
+ Deposit GHO and earn up to {(rate * 100).toFixed(2)}% APR ({
+ sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: { xs: '8px', xsm: '6px' },
- border: `1px solid ${theme.palette.divider}`,
+ border: `1px solid ${figVars['border-0']}`,
p: 4,
mb: 6,
- background: theme.palette.background.paper,
- })}
+ }}
>
-
+ Staking APR
-
+ {
valueUSD="0"
dataCy="sghoBalanceBox_loggedOut"
bottomLineTitle={
-
+ Cooldown period
}
bottomLineComponent={
-
+ Instant
}
>
-
+ Withdraw
diff --git a/src/modules/sGho/SGhoSavingsRate.tsx b/src/modules/sGho/SGhoSavingsRate.tsx
index 8b34d53daf..bdefe0d4ea 100644
--- a/src/modules/sGho/SGhoSavingsRate.tsx
+++ b/src/modules/sGho/SGhoSavingsRate.tsx
@@ -27,29 +27,29 @@ export const SGhoSavingsRate = ({ totalDepositedUSD, rate }: SGhoSavingsRateProp
sx={{ mb: 4 }}
>
-
+ Total Deposited
-
+ APR
-
+
-
+ APY, fixed rate
-
+
diff --git a/src/modules/sGho/SGhoWithdrawRow.tsx b/src/modules/sGho/SGhoWithdrawRow.tsx
index 25d520ef47..54608e0d3d 100644
--- a/src/modules/sGho/SGhoWithdrawRow.tsx
+++ b/src/modules/sGho/SGhoWithdrawRow.tsx
@@ -20,18 +20,18 @@ export const SGhoWithdrawRow = ({ balance, balanceUSD, onWithdraw }: SGhoWithdra
valueUSD={balanceUSD}
dataCy="sghoBalanceBox"
bottomLineTitle={
-
+ Cooldown period
}
bottomLineComponent={
-
+ Instant
}
>
{
return (
{
Your info
-
+ Please connect a wallet to view your personal information here.
diff --git a/src/modules/staking/BuyWithFiat.tsx b/src/modules/staking/BuyWithFiat.tsx
index 51a2de843e..cb3d680080 100644
--- a/src/modules/staking/BuyWithFiat.tsx
+++ b/src/modules/staking/BuyWithFiat.tsx
@@ -34,7 +34,7 @@ export const BuyWithFiat = ({ cryptoSymbol, networkMarketName, funnel }: BuyWith
return isAvailable ? (
<>
(
diff --git a/src/modules/staking/GetABPToken.tsx b/src/modules/staking/GetABPToken.tsx
index 43ee4ae77c..a0ba9828df 100644
--- a/src/modules/staking/GetABPToken.tsx
+++ b/src/modules/staking/GetABPToken.tsx
@@ -26,7 +26,7 @@ export const GetABPToken = () => {
<>
{
diff --git a/src/modules/staking/GetGhoToken.tsx b/src/modules/staking/GetGhoToken.tsx
index 65df1456ab..a0d17fe09f 100644
--- a/src/modules/staking/GetGhoToken.tsx
+++ b/src/modules/staking/GetGhoToken.tsx
@@ -16,7 +16,7 @@ export const GetGhoToken = () => {
<>
= ({
maxSlash,
children,
}) => {
- const { breakpoints } = useTheme();
- const xsm = useMediaQuery(breakpoints.up('xsm'));
const now = useCurrentTimestamp(1);
const { openSwitch } = useModalContext();
@@ -125,7 +117,7 @@ export const GhoStakingPanel: React.FC = ({
// const distributionEnded = Date.now() / 1000 > Number(stakeData.distributionEnd);
return (
-
+ = ({
/>
-
+
Total deposited:{' '}
= ({
({
+ sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', xsm: 'center' },
flexDirection: { xs: 'column', xsm: 'row' },
gap: { xs: 0, xsm: 2 },
borderRadius: { xs: 0, xsm: '6px' },
- border: { xs: 'unset', xsm: `1px solid ${theme.palette.divider}` },
+ border: { xs: 'unset', xsm: `1px solid ${figVars['border-0']}` },
p: { xs: 0, xsm: 4 },
- background: {
- xs: 'unset',
- xsm: theme.palette.background.paper,
- },
position: 'relative',
'&:after': {
content: "''",
@@ -185,9 +173,9 @@ export const GhoStakingPanel: React.FC = ({
left: '-16px',
width: 'calc(100% + 32px)',
height: '1px',
- bgcolor: { xs: 'divider', xsm: 'transparent' },
+ bgcolor: { xs: 'border-2', xsm: 'transparent' },
},
- })}
+ }}
>
= ({
- sGHO
+
+ sGHO
+
-
+
Total deposited{' '}
= ({
}}
>
-
+ Deposit APR
@@ -270,13 +253,10 @@ export const GhoStakingPanel: React.FC = ({
mb: { xs: 3, xsm: 0 },
}}
>
-
+ Max slashing
-
+ = ({
mb: { xs: 3, xsm: 0 },
}}
>
-
+ Wallet Balance
@@ -301,9 +278,8 @@ export const GhoStakingPanel: React.FC = ({
{+availableToStake === 0 ? (
Get GHO
@@ -311,10 +287,9 @@ export const GhoStakingPanel: React.FC = ({
) : (
Deposit
@@ -371,17 +346,17 @@ export const GhoStakingPanel: React.FC = ({
bottomLineComponent={
<>
{isCooldownActive && !isUnstakeWindowActive ? (
-
+
) : isUnstakeWindowActive ? (
-
+
) : (
-
+ Instant
)}
@@ -398,15 +373,15 @@ export const GhoStakingPanel: React.FC = ({
pt: 2,
}}
>
-
+ Amount in cooldown
@@ -419,7 +394,7 @@ export const GhoStakingPanel: React.FC = ({
{isUnstakeWindowActive && (
= ({
{availableToReactivateCooldown && (
+
Reactivate cooldown period to unstake{' '}
{Number(
@@ -445,7 +416,7 @@ export const GhoStakingPanel: React.FC = ({
}
>
= ({
{isCooldownActive && !isUnstakeWindowActive && (
= ({
{availableToReactivateCooldown && (
+
Reactivate cooldown period to unstake{' '}
{Number(
@@ -489,7 +456,7 @@ export const GhoStakingPanel: React.FC = ({
}
>
= ({
{!isCooldownActive && (
= ({
dataCy={`rewardBox_${stakedToken}`}
bottomLineComponent={}
>
-
-
- Claim
-
-
+ Claim
+
{children}
diff --git a/src/modules/staking/SavingsGhoProgram.tsx b/src/modules/staking/SavingsGhoProgram.tsx
index 0221cf55d3..c8b448adf3 100644
--- a/src/modules/staking/SavingsGhoProgram.tsx
+++ b/src/modules/staking/SavingsGhoProgram.tsx
@@ -71,7 +71,7 @@ export const SavingsGhoProgram = () => {
diff --git a/src/modules/staking/StakeActionBox.tsx b/src/modules/staking/StakeActionBox.tsx
index 9bac225c4b..69e30e887d 100644
--- a/src/modules/staking/StakeActionBox.tsx
+++ b/src/modules/staking/StakeActionBox.tsx
@@ -1,5 +1,7 @@
import { Box, Typography } from '@mui/material';
import React, { ReactNode } from 'react';
+import { figVars } from 'src/utils/figmaColors';
+import { darkScheme } from 'src/utils/theme';
import { FormattedNumber } from '../../components/primitives/FormattedNumber';
import { Row } from '../../components/primitives/Row';
@@ -29,11 +31,11 @@ export const StakeActionBox = ({
}: StakeActionBoxProps) => {
return (
({
+ sx={{
flex: 1,
display: 'flex',
borderRadius: '6px',
- border: `1px solid ${theme.palette.divider}`,
+ border: `1px solid ${figVars['border-0']}`,
position: 'relative',
'&:after': {
content: "''",
@@ -43,26 +45,27 @@ export const StakeActionBox = ({
bottom: -1,
left: -1,
right: -1,
- background: gradientBorder ? theme.palette.gradients.aaveGradient : 'transparent',
+ background: gradientBorder ? figVars['purple-1'] : 'transparent',
},
- })}
+ }}
>
({
+ sx={{
flex: 1,
p: 4,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
borderRadius: '6px',
- background: theme.palette.background.paper,
+ backgroundColor: figVars['surface-elevated'],
+ ...darkScheme({ backgroundColor: figVars['bg-2'] }),
position: 'relative',
zIndex: 2,
- })}
+ }}
data-cy={dataCy}
>
-
+
{title}
@@ -70,28 +73,23 @@ export const StakeActionBox = ({
value={value}
visibleDecimals={2}
variant="secondary21"
- color={+value === 0 ? 'text.muted' : 'text.primary'}
+ color={+value === 0 ? 'fg-3' : 'fg-1'}
data-cy={`amountNative`}
/>
{children}
-
+
{bottomLineComponent}
{cooldownAmount}
diff --git a/src/modules/staking/StakingHeader.tsx b/src/modules/staking/StakingHeader.tsx
index 2d37e8649d..0e49f8cb99 100644
--- a/src/modules/staking/StakingHeader.tsx
+++ b/src/modules/staking/StakingHeader.tsx
@@ -1,16 +1,7 @@
-import { ChainId } from '@aave/contract-helpers';
import { Trans } from '@lingui/macro';
-import { Box, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
-import { ChainAvailabilityText } from 'src/components/ChainAvailabilityText';
+import { PageHeader } from 'src/components/PageHeader/PageHeader';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
-import { Row } from 'src/components/primitives/Row';
-import { TextWithTooltip } from 'src/components/TextWithTooltip';
-import { TopInfoPanel } from 'src/components/TopInfoPanel/TopInfoPanel';
-import { useRootStore } from 'src/store/root';
-import { GENERAL } from 'src/utils/events';
-
-import { Link } from '../../components/primitives/Link';
-import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
interface StakingHeaderProps {
tvl: {
@@ -21,110 +12,36 @@ interface StakingHeaderProps {
}
export const StakingHeader: React.FC = ({ tvl, stkEmission, loading }) => {
- const theme = useTheme();
- const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
-
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
- const trackEvent = useRootStore((store) => store.trackEvent);
-
const total = Object.values(tvl || {}).reduce((acc, item) => acc + item, 0);
- const TotalFundsTooltip = () => {
- return (
-
-
- {Object.entries(tvl)
- .sort((a, b) => b[1] - a[1])
- .map(([key, value]) => (
-
-
-
- ))}
-
-
- );
- };
-
return (
-
-
-
- {/* */}
-
- Safety Module
-
-
-
-
-
- The Safety Module has been upgraded to{' '}
-
- Umbrella
-
- , a new system that introduces automated slashing, aToken staking, and improved
- incentives design.
-
-
-
-
- AAVE and ABPT holders (Ethereum network only) can stake their assets in the Safety
- Module to add more security to the protocol and earn Safety Incentives. In the case of
- a shortfall event, your stake can be slashed to cover the deficit, providing an
- additional layer of protection for the protocol.
- {' '}
-
- trackEvent(GENERAL.EXTERNAL_LINK, {
- Link: 'Staking Risks',
- })
- }
- >
- Learn more about risks involved
-
-
-
+
+ The Safety Module has been upgraded to Umbrella, a new system that introduces automated
+ slashing, aToken staking, and improved incentives design.
+
}
>
-
- Funds in the Safety Module
-
-
- }
- loading={loading}
- >
+ Funds in the Safety Module} loading={loading}>
-
+
- Total emission per day} loading={loading}>
+ Total emission per day} loading={loading}>
-
-
+
+
);
};
diff --git a/src/modules/staking/StakingPanel.tsx b/src/modules/staking/StakingPanel.tsx
index c9657b9f10..63e1e72911 100644
--- a/src/modules/staking/StakingPanel.tsx
+++ b/src/modules/staking/StakingPanel.tsx
@@ -2,16 +2,7 @@ import { GetUserStakeUIDataHumanized } from '@aave/contract-helpers/dist/esm/V3-
import { valueToBigNumber } from '@aave/math-utils';
import { RefreshIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
-import {
- Box,
- Button,
- Paper,
- Stack,
- SvgIcon,
- Typography,
- useMediaQuery,
- useTheme,
-} from '@mui/material';
+import { Box, Button, Paper, Stack, SvgIcon, Typography } from '@mui/material';
import { BigNumber } from 'ethers';
import { formatEther, formatUnits } from 'ethers/lib/utils';
import React from 'react';
@@ -24,7 +15,10 @@ import { SecondsToString } from 'src/components/SecondsToString';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
import { StakeTokenFormatted } from 'src/hooks/stake/useGeneralStakeUiData';
import { useCurrentTimestamp } from 'src/hooks/useCurrentTimestamp';
+import { stakePanelActionSx } from 'src/utils/buttonStyles';
+import { cardPaddingSx, panelStatLabelSx } from 'src/utils/cardStyles';
import { GENERAL } from 'src/utils/events';
+import { figVars } from 'src/utils/figmaColors';
import { StakeActionBox } from './StakeActionBox';
import { StakingPanelSkeleton } from './StakingPanelSkeleton';
@@ -63,8 +57,6 @@ export const StakingPanel: React.FC = ({
maxSlash,
children,
}) => {
- const { breakpoints } = useTheme();
- const xsm = useMediaQuery(breakpoints.up('xsm'));
const now = useCurrentTimestamp(1);
if (!stakeData || !stakeUserData) {
@@ -122,7 +114,7 @@ export const StakingPanel: React.FC = ({
const distributionEnded = Date.now() / 1000 > Number(stakeData.distributionEnd);
return (
-
+ = ({
/>
-
+
Total staked:{' '}
= ({
({
+ sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', xsm: 'center' },
flexDirection: { xs: 'column', xsm: 'row' },
gap: { xs: 0, xsm: 2 },
borderRadius: { xs: 0, xsm: '6px' },
- border: { xs: 'unset', xsm: `1px solid ${theme.palette.divider}` },
+ border: { xs: 'unset', xsm: `1px solid ${figVars['border-0']}` },
p: { xs: 0, xsm: 4 },
- background: {
- xs: 'unset',
- xsm: theme.palette.background.paper,
- },
position: 'relative',
'&:after': {
content: "''",
@@ -182,9 +170,9 @@ export const StakingPanel: React.FC = ({
left: '-16px',
width: 'calc(100% + 32px)',
height: '1px',
- bgcolor: { xs: 'divider', xsm: 'transparent' },
+ bgcolor: { xs: 'border-2', xsm: 'transparent' },
},
- })}
+ }}
>
= ({
- {stakedToken}
+
+ {stakedToken}
+
-
+
Total staked{' '}
= ({
}}
>
-
+ Staking APR
{distributionEnded && (
@@ -262,7 +245,7 @@ export const StakingPanel: React.FC = ({
href="https://governance.aave.com"
sx={{ textDecoration: 'underline' }}
variant="caption"
- color="text.secondary"
+ color="fg-2"
>
Learn more
@@ -276,7 +259,7 @@ export const StakingPanel: React.FC = ({
sx={{ mr: 2 }}
value={stakeData.stakeApyFormatted}
percent
- variant="secondary14"
+ variant="h5"
/>
@@ -289,13 +272,10 @@ export const StakingPanel: React.FC = ({
mb: { xs: 3, xsm: 0 },
}}
>
-
+ Max slashing
-
+ = ({
mb: { xs: 3, xsm: 0 },
}}
>
-
+ Wallet Balance
@@ -319,10 +296,9 @@ export const StakingPanel: React.FC = ({
Stake
@@ -349,7 +325,7 @@ export const StakingPanel: React.FC = ({
>
= ({
bottomLineComponent={
<>
{isCooldownActive && !isUnstakeWindowActive ? (
-
+
) : isUnstakeWindowActive ? (
-
+
) : (
-
+
)}
@@ -443,15 +419,15 @@ export const StakingPanel: React.FC = ({
pt: 2,
}}
>
-
+ Amount in cooldown
@@ -464,7 +440,7 @@ export const StakingPanel: React.FC = ({
{isUnstakeWindowActive && (
= ({
{availableToReactivateCooldown && (
+
Reactivate cooldown period to unstake{' '}
{Number(
@@ -490,7 +462,7 @@ export const StakingPanel: React.FC = ({
}
>
= ({
{isCooldownActive && !isUnstakeWindowActive && (
= ({
{availableToReactivateCooldown && (
+
Reactivate cooldown period to unstake{' '}
{Number(
@@ -534,7 +502,7 @@ export const StakingPanel: React.FC = ({
}
>
= ({
{!isCooldownActive && (
= ({
}
>
= ({
onClick={onStakeRewardClaimAction}
disabled={stakeUserData?.userIncentivesToClaim === '0'}
data-cy={`claimBtn_${stakedToken}`}
- sx={{
- flex: 1,
- mb: { xs: 2, sm: 0 },
- mr: { xs: 0, sm: 1 },
- }}
+ sx={{ flex: 1 }}
>
Claim
@@ -603,7 +566,7 @@ export const StakingPanel: React.FC = ({
onClick={onStakeRewardClaimRestakeAction}
disabled={stakeUserData?.userIncentivesToClaim === '0'}
data-cy={`restakeBtn_${stakedToken}`}
- style={{ flex: 1 }} // marginLeft adds space between buttons
+ sx={{ flex: 1 }}
>
Restake
diff --git a/src/modules/staking/StakingPanelNoWallet.tsx b/src/modules/staking/StakingPanelNoWallet.tsx
index 918ec659bc..1194d6d02b 100644
--- a/src/modules/staking/StakingPanelNoWallet.tsx
+++ b/src/modules/staking/StakingPanelNoWallet.tsx
@@ -7,6 +7,7 @@ import { TokenIcon } from 'src/components/primitives/TokenIcon';
import { StakeTokenFormatted, useGeneralStakeUiData } from 'src/hooks/stake/useGeneralStakeUiData';
import { useRootStore } from 'src/store/root';
import { CustomMarket } from 'src/ui-config/marketsConfig';
+import { figVars } from 'src/utils/figmaColors';
export interface StakingPanelNoWalletProps {
description?: React.ReactNode;
@@ -39,15 +40,14 @@ export const StakingPanelNoWallet: React.FC = ({
return (
({
+ sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
borderRadius: '6px',
- border: `1px solid ${theme.palette.divider}`,
+ border: `1px solid ${figVars['border-0']}`,
p: 4,
- background: theme.palette.background.paper,
width: '250px',
height: '68px',
margin: '0 auto',
@@ -61,7 +61,7 @@ export const StakingPanelNoWallet: React.FC = ({
height: '1px',
bgcolor: 'transparent',
},
- })}
+ }}
>
= ({
>
-
+
{stakedToken}
@@ -87,7 +87,7 @@ export const StakingPanelNoWallet: React.FC = ({
>
{stakedToken !== 'GHO' && (
-
+ Staking APR
@@ -96,14 +96,14 @@ export const StakingPanelNoWallet: React.FC = ({
)}
{stakedToken === 'GHO' && (
-
+ Incentives APR
diff --git a/src/modules/staking/StakingPanelSkeleton.tsx b/src/modules/staking/StakingPanelSkeleton.tsx
index b7f5156af7..ad008f6fb3 100644
--- a/src/modules/staking/StakingPanelSkeleton.tsx
+++ b/src/modules/staking/StakingPanelSkeleton.tsx
@@ -1,8 +1,9 @@
import { Paper, Skeleton, Stack } from '@mui/material';
+import { cardPaddingSx } from 'src/utils/cardStyles';
export const StakingPanelSkeleton = () => {
return (
-
+
diff --git a/src/modules/stkGho/StkGhoCard.tsx b/src/modules/stkGho/StkGhoCard.tsx
index 829fa963e1..a7c1619236 100644
--- a/src/modules/stkGho/StkGhoCard.tsx
+++ b/src/modules/stkGho/StkGhoCard.tsx
@@ -1,7 +1,6 @@
import { StakeUIUserData } from '@aave/contract-helpers/dist/esm/V3-uiStakeDataProvider-contract/types';
import { Trans } from '@lingui/macro';
-import { Box, Paper, Typography, useMediaQuery, useTheme } from '@mui/material';
-import { Warning } from 'src/components/primitives/Warning';
+import { Alert, Box, Paper, Typography } from '@mui/material';
import { StakeTokenFormatted, useGeneralStakeUiData } from 'src/hooks/stake/useGeneralStakeUiData';
import { useUserStakeUiData } from 'src/hooks/stake/useUserStakeUiData';
import { useModalContext } from 'src/hooks/useModal';
@@ -16,8 +15,6 @@ export const StkGhoCard = () => {
const [trackEvent, currentMarketData] = useRootStore(
useShallow((store) => [store.trackEvent, store.currentMarketData])
);
- const { breakpoints } = useTheme();
- const downToXsm = useMediaQuery(breakpoints.down('xsm'));
const { data: stakeGeneralResult } = useGeneralStakeUiData(currentMarketData);
const { data: stakeUserResult } = useUserStakeUiData(currentMarketData);
@@ -34,10 +31,9 @@ export const StkGhoCard = () => {
return (
{
-
+ Rewards for legacy Savings GHO have ended. Migrate to continue earning.
-
+
{
- const { breakpoints } = useTheme();
- const xsm = useMediaQuery(breakpoints.up('xsm'));
const { openSwitch } = useModalContext();
const { chainId: targetChainId } = useSavingsMarketData();
@@ -42,8 +42,8 @@ export const StkGhoDepositRow = ({
// When the user holds a legacy position, migration is the primary action:
// invert the emphasis so Migrate is contained and Deposit/Get GHO is outlined.
- const depositVariant = hasLegacyPosition ? 'outlined' : 'contained';
- const migrateVariant = hasLegacyPosition ? 'contained' : 'outlined';
+ const depositVariant = hasLegacyPosition ? 'tertiary' : 'contained';
+ const migrateVariant = hasLegacyPosition ? 'contained' : 'tertiary';
const handleGetGho = () => {
openSwitch('', targetChainId);
@@ -56,31 +56,18 @@ export const StkGhoDepositRow = ({
cursor: meritIncentives ? 'pointer' : 'default',
}}
>
-
+ APR
-
+
{meritIncentives && }
);
return (
- ({
- display: 'flex',
- alignItems: { xs: 'stretch', xsm: 'center' },
- justifyContent: 'space-between',
- flexDirection: { xs: 'column', xsm: 'row' },
- gap: { xs: 4, xsm: 4 },
- borderRadius: { xs: '8px', xsm: '6px' },
- border: `1px solid ${theme.palette.divider}`,
- p: 4,
- mb: 6,
- background: theme.palette.background.paper,
- })}
- >
+
@@ -88,28 +75,20 @@ export const StkGhoDepositRow = ({
stkGHO
-
+ Available to deposit:
-
+
{meritIncentives ? (
Deposit
@@ -142,8 +120,7 @@ export const StkGhoDepositRow = ({
Get GHO
@@ -154,8 +131,7 @@ export const StkGhoDepositRow = ({
variant={migrateVariant}
onClick={onMigrate}
disabled={!hasLegacyPosition}
- fullWidth={!xsm}
- sx={{ minWidth: { xs: '140px', xsm: '96px' }, height: '36px' }}
+ sx={depositRowActionSx}
data-cy={`migrateBtn_${stakedToken.toUpperCase()}`}
>
Migrate
diff --git a/src/modules/stkGho/StkGhoSavingsRate.tsx b/src/modules/stkGho/StkGhoSavingsRate.tsx
index 29b33163b2..eabc9f3501 100644
--- a/src/modules/stkGho/StkGhoSavingsRate.tsx
+++ b/src/modules/stkGho/StkGhoSavingsRate.tsx
@@ -42,22 +42,22 @@ export const StkGhoSavingsRate = ({ totalDepositedUSD }: StkGhoSavingsRateProps)
sx={{ mb: 4 }}
>
-
+ Total Deposited
-
+ APY
-
+
diff --git a/src/modules/stkGho/StkGhoWithdrawRow.tsx b/src/modules/stkGho/StkGhoWithdrawRow.tsx
index eae5dfefab..b803b97c5a 100644
--- a/src/modules/stkGho/StkGhoWithdrawRow.tsx
+++ b/src/modules/stkGho/StkGhoWithdrawRow.tsx
@@ -50,18 +50,18 @@ export const StkGhoWithdrawRow = ({
valueUSD={stakedUSD}
dataCy={`stakedBox_${stakedToken}`}
bottomLineTitle={
-
+ Cooldown period
}
bottomLineComponent={
-
+ Instant
}
>
+
{!isCooldownActive && !isUnstakeWindowActive ? (
<>
-
+
>
) : (
diff --git a/src/modules/umbrella/AmountStakedUnderlyingItem.tsx b/src/modules/umbrella/AmountStakedUnderlyingItem.tsx
index 3dbde29b5f..227fbffb27 100644
--- a/src/modules/umbrella/AmountStakedUnderlyingItem.tsx
+++ b/src/modules/umbrella/AmountStakedUnderlyingItem.tsx
@@ -6,13 +6,7 @@ import { useRootStore } from 'src/store/root';
import { usePreviewRedeem } from './hooks/usePreviewRedeem';
-export const AmountStakedUnderlyingItem = ({
- stakeData,
- isMobile,
-}: {
- stakeData: MergedStakeData;
- isMobile?: boolean;
-}) => {
+export const AmountStakedUnderlyingItem = ({ stakeData }: { stakeData: MergedStakeData }) => {
const currentMarketData = useRootStore((s) => s.currentMarketData);
const chainId = currentMarketData?.chainId;
@@ -33,13 +27,8 @@ export const AmountStakedUnderlyingItem = ({
const assetUnderlyingAmount = isGhoToken ? formattedGhoAmount : sharesEquivalentAssets;
return (
-
-
+
+
);
};
diff --git a/src/modules/umbrella/AvailableToClaimItem.tsx b/src/modules/umbrella/AvailableToClaimItem.tsx
index 86d384ba36..7132927020 100644
--- a/src/modules/umbrella/AvailableToClaimItem.tsx
+++ b/src/modules/umbrella/AvailableToClaimItem.tsx
@@ -7,13 +7,7 @@ import { ListValueColumn } from '../dashboard/lists/ListValueColumn';
import { AmountAvailableItem } from './helpers/AmountAvailableItem';
import { MultiIconWithTooltip } from './helpers/MultiIcon';
-export const AvailableToClaimItem = ({
- stakeData,
- isMobile,
-}: {
- stakeData: MergedStakeData;
- isMobile?: boolean;
-}) => {
+export const AvailableToClaimItem = ({ stakeData }: { stakeData: MergedStakeData }) => {
const icons = stakeData.formattedRewards.map((reward) => ({
src: reward.rewardTokenSymbol,
aToken: reward.aToken,
@@ -30,13 +24,7 @@ export const AvailableToClaimItem = ({
);
return (
-
+ {
return (
-
+ Rewards available to claim
diff --git a/src/modules/umbrella/AvailableToStakeItem.tsx b/src/modules/umbrella/AvailableToStakeItem.tsx
index d8be5de1f0..9353ed3517 100644
--- a/src/modules/umbrella/AvailableToStakeItem.tsx
+++ b/src/modules/umbrella/AvailableToStakeItem.tsx
@@ -7,13 +7,7 @@ import { MergedStakeData } from 'src/hooks/stake/useUmbrellaSummary';
import { AmountAvailableItem } from './helpers/AmountAvailableItem';
import { MultiIconWithTooltip } from './helpers/MultiIcon';
-export const AvailableToStakeItem = ({
- stakeData,
- isMobile,
-}: {
- stakeData: MergedStakeData;
- isMobile?: boolean;
-}) => {
+export const AvailableToStakeItem = ({ stakeData }: { stakeData: MergedStakeData }) => {
const {
stataTokenAssetBalance: underlyingWaTokenBalance,
aTokenBalanceAvailableToStake,
@@ -47,17 +41,12 @@ export const AvailableToStakeItem = ({
Number(aTokenBalanceAvailableToStake);
return (
-
+
{stakeData.underlyingIsStataToken ? (
-
+ Your balance of assets that are available to stake
diff --git a/src/modules/umbrella/StakeAssets/StakeAssetName.tsx b/src/modules/umbrella/StakeAssets/StakeAssetName.tsx
index 0f5c6f2113..4b51453daf 100644
--- a/src/modules/umbrella/StakeAssets/StakeAssetName.tsx
+++ b/src/modules/umbrella/StakeAssets/StakeAssetName.tsx
@@ -26,14 +26,14 @@ export const StakeAssetName = ({
-
+
Stake {symbol}
-
+
Total staked:{' '}
+ Target liquidity
}
@@ -61,7 +61,7 @@ export const StakeAssetName = ({
-
+ Reward APY at target liquidity
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaAssetsList.tsx b/src/modules/umbrella/StakeAssets/UmbrellaAssetsList.tsx
index 9387033cc8..9c49e5a4f1 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaAssetsList.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaAssetsList.tsx
@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro';
-import { Box, useMediaQuery } from '@mui/material';
+import { useMediaQuery, useTheme } from '@mui/material';
import { useMemo, useState } from 'react';
+import { TABLE_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
@@ -20,23 +21,23 @@ const listHeaders = [
sortKey: 'symbol',
},
{
- title: ,
+ title: ,
sortKey: 'totalAPY',
},
{
- title: ,
+ title: ,
sortKey: 'stakeTokenUnderlyingBalance',
},
{
- title: ,
+ title: ,
sortKey: 'stakeSharesTokens',
},
{
- title: Available to Stake,
+ title: Av. to Stake,
sortKey: 'totalAvailableToStake',
},
{
- title: Available to Claim,
+ title: Av. to Claim,
sortKey: 'totalAvailableToClaim',
},
{
@@ -55,7 +56,8 @@ export default function UmbrellaAssetsList({
stakedDataWithTokenBalances,
isLoadingStakedDataWithTokenBalances,
}: UmbrelaAssetsListProps) {
- const isTableChangedToCards = useMediaQuery('(max-width:1125px)');
+ const theme = useTheme();
+ const isTableChangedToCards = useMediaQuery(theme.breakpoints.down(TABLE_CARDS_BELOW));
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
@@ -105,30 +107,19 @@ export default function UmbrellaAssetsList({
});
}, [stakedDataWithTokenBalances, sortName, sortDesc]);
- if (loading || isLoadingStakedDataWithTokenBalances) {
- return isTableChangedToCards ? (
- <>
-
-
-
- >
- ) : (
-
-
-
-
-
-
- );
- }
+ const isLoading = loading || isLoadingStakedDataWithTokenBalances;
+ const Loader = isTableChangedToCards
+ ? UmbrellaAssetsListMobileItemLoader
+ : UmbrellaAssetsListItemLoader;
+ const Item = isTableChangedToCards ? UmbrellaAssetsListMobileItem : UmbrellaStakeAssetsListItem;
+
// Hide list when no results, via search term or if a market has no assets
- if (stakedDataWithTokenBalances == undefined || stakedDataWithTokenBalances.length === 0)
- return null;
+ if (!isLoading && sortedData.length === 0) return null;
return (
<>
{!isTableChangedToCards && (
-
+
{listHeaders.map((col) => (
)}
- {sortedData.map((umbrellaStakeAsset, index) =>
- isTableChangedToCards ? (
-
- ) : (
-
- )
- )}
+ {isLoading
+ ? Array.from({ length: isTableChangedToCards ? 3 : 4 }, (_, i) => )
+ : sortedData.map((umbrellaStakeAsset, index) => (
+
+ ))}
>
);
}
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListContainer.tsx b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListContainer.tsx
index 22ab96706f..a1c6a4e1db 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListContainer.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListContainer.tsx
@@ -1,11 +1,15 @@
import { Trans } from '@lingui/macro';
-import { useMediaQuery, useTheme } from '@mui/material';
+import { Box, Paper, useMediaQuery, useTheme } from '@mui/material';
import { useState } from 'react';
-import { ListWrapper } from 'src/components/lists/ListWrapper';
+import { AssetsFilterBar } from 'src/components/AssetsFilterBar';
import { NoSearchResults } from 'src/components/NoSearchResults';
-import { TitleWithSearchBar } from 'src/components/TitleWithSearchBar';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useUmbrellaSummary } from 'src/hooks/stake/useUmbrellaSummary';
+import { useCoingeckoCategories } from 'src/hooks/useCoinGeckoCategories';
+import {
+ AssetCategory,
+ matchesSelectedCategories,
+} from 'src/modules/markets/utils/assetCategories';
import { useRootStore } from 'src/store/root';
import { useShallow } from 'zustand/shallow';
@@ -19,54 +23,71 @@ export const UmbrellaAssetsListContainer = () => {
const { data: stakedDataWithTokenBalances, loading: isLoadingStakedDataWithTokenBalances } =
useUmbrellaSummary(currentMarketData);
+ const {
+ data: categoryData,
+ isLoading: isLoadingCategories,
+ error: categoriesError,
+ } = useCoingeckoCategories();
const [searchTerm, setSearchTerm] = useState('');
+ const [inWalletOnly, setInWalletOnly] = useState(false);
+ const [selectedCategories, setSelectedCategories] = useState([]);
const { breakpoints } = useTheme();
const sm = useMediaQuery(breakpoints.down('sm'));
- const filteredData = stakedDataWithTokenBalances?.stakeData.filter((res) => {
- if (!searchTerm) return true;
- const term = searchTerm.toLowerCase().trim();
-
- return res.name.toLowerCase().includes(term) || res.iconSymbol.toLowerCase().includes(term);
- });
+ const filteredData = stakedDataWithTokenBalances?.stakeData
+ // Search by asset name or symbol
+ .filter((res) => {
+ if (!searchTerm) return true;
+ const term = searchTerm.toLowerCase().trim();
+ return res.name.toLowerCase().includes(term) || res.iconSymbol.toLowerCase().includes(term);
+ })
+ // "In Wallet": only assets the user holds in their wallet (raw underlying token balance)
+ .filter((res) => !inWalletOnly || Number(res.formattedBalances.underlyingTokenBalance) > 0)
+ // Category filter (shares the markets page's dynamic CoinGecko categorization)
+ .filter((res) =>
+ matchesSelectedCategories(
+ res.symbol,
+ selectedCategories,
+ categoryData?.stablecoinSymbols,
+ categoryData?.ethCorrelatedSymbols
+ )
+ );
const noStakeAssetsConfigured =
!isLoadingStakedDataWithTokenBalances && !stakedDataWithTokenBalances;
return (
- Assets to stake}
- searchPlaceholder={sm ? 'Search asset' : 'Search asset name or symbol'}
- />
- }
- >
-
+
- {noStakeAssetsConfigured ? (
-
- ) : (
- !loading &&
- !isLoadingStakedDataWithTokenBalances &&
- filteredData?.length === 0 && (
-
- We couldn't find any assets related to your search. Try again with a different
- asset name, symbol, or address.
-
- }
- />
- )
- )}
-
+
+
+
+ {noStakeAssetsConfigured ? (
+
+ ) : (
+ !loading &&
+ !isLoadingStakedDataWithTokenBalances &&
+ filteredData?.length === 0 && (
+ We couldn't find any assets related to your search.}
+ />
+ )
+ )}
+
+
);
};
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListItemLoader.tsx b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListItemLoader.tsx
index 3b94dae30b..12ed93cf08 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListItemLoader.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListItemLoader.tsx
@@ -5,9 +5,9 @@ import { ListItem } from '../../../components/lists/ListItem';
export const UmbrellaAssetsListItemLoader = () => {
return (
-
+
-
+
@@ -29,7 +29,7 @@ export const UmbrellaAssetsListItemLoader = () => {
-
+
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItem.tsx b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItem.tsx
index f553675ce3..89f8aa340f 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItem.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItem.tsx
@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro';
import { Box } from '@mui/material';
-import { ListColumn } from 'src/components/lists/ListColumn';
import { MergedStakeData } from 'src/hooks/stake/useUmbrellaSummary';
import { StakingDropdown } from 'src/modules/umbrella/helpers/StakingDropdown';
import { useRootStore } from 'src/store/root';
@@ -23,7 +22,7 @@ export const UmbrellaAssetsListMobileItem = ({ ...umbrellaStakeAsset }: MergedSt
return (
-
+
-
+ } captionVariant="description" mb={3}>
-
-
+
+
-
+ } captionVariant="description" mb={3} align="flex-start">
@@ -64,24 +55,16 @@ export const UmbrellaAssetsListMobileItem = ({ ...umbrellaStakeAsset }: MergedSt
mb={3}
align="flex-start"
>
-
+ Available to claim} captionVariant="description" mb={3}>
-
-
+
+
-
+
);
};
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItemLoader.tsx b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItemLoader.tsx
index c4100aa988..354338c455 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItemLoader.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaAssetsListMobileItemLoader.tsx
@@ -9,7 +9,7 @@ export const UmbrellaAssetsListMobileItemLoader = () => {
-
+
diff --git a/src/modules/umbrella/StakeAssets/UmbrellaStakeAssetsListItem.tsx b/src/modules/umbrella/StakeAssets/UmbrellaStakeAssetsListItem.tsx
index dac6868262..16f5537d5d 100644
--- a/src/modules/umbrella/StakeAssets/UmbrellaStakeAssetsListItem.tsx
+++ b/src/modules/umbrella/StakeAssets/UmbrellaStakeAssetsListItem.tsx
@@ -16,7 +16,7 @@ export const UmbrellaStakeAssetsListItem = ({ ...umbrellaStakeAsset }: MergedSta
const [currentNetworkConfig] = useRootStore(useShallow((store) => [store.currentNetworkConfig]));
return (
-
+
-
+
diff --git a/src/modules/umbrella/StakeCooldownModalContent.tsx b/src/modules/umbrella/StakeCooldownModalContent.tsx
index 9a8e6a3496..41f7928fdd 100644
--- a/src/modules/umbrella/StakeCooldownModalContent.tsx
+++ b/src/modules/umbrella/StakeCooldownModalContent.tsx
@@ -2,7 +2,7 @@ import { valueToBigNumber } from '@aave/math-utils';
import { ArrowDownIcon, CalendarIcon } from '@heroicons/react/outline';
import { ArrowNarrowRightIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
-import { Box, Checkbox, FormControlLabel, Stack, SvgIcon, Typography } from '@mui/material';
+import { Alert, Box, Checkbox, FormControlLabel, Stack, SvgIcon, Typography } from '@mui/material';
import { BigNumber } from 'bignumber.js';
import dayjs from 'dayjs';
import { parseUnits } from 'ethers/lib/utils';
@@ -10,7 +10,6 @@ import React, { useState } from 'react';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Link } from 'src/components/primitives/Link';
import { TokenIcon } from 'src/components/primitives/TokenIcon';
-import { Warning } from 'src/components/primitives/Warning';
import { TxErrorView } from 'src/components/transactions/FlowCommons/Error';
import { GasEstimationError } from 'src/components/transactions/FlowCommons/GasEstimationError';
import { TxSuccessView } from 'src/components/transactions/FlowCommons/Success';
@@ -173,26 +172,22 @@ export const StakeCooldownModalContent = ({ stakeData }: { stakeData: MergedStak
pb: '30px',
}}
>
-
+ Amount available to unstake
-
+
@@ -208,18 +203,18 @@ export const StakeCooldownModalContent = ({ stakeData }: { stakeData: MergedStak
pb: '30px',
}}
>
-
+ Unstake window
-
+
{dateMessage(stakeCooldownSeconds)}
-
+
{dateMessage(stakeCooldownSeconds + stakeUnstakeWindow)}
@@ -328,14 +323,12 @@ export const StakeCooldownModalContent = ({ stakeData }: { stakeData: MergedStak
)}
-
-
-
- If you DO NOT unstake within {timeMessage(stakeUnstakeWindow)} of unstake window, you
- will need to activate cooldown process again.
-
-
-
+
+
+ If you DO NOT unstake within {timeMessage(stakeUnstakeWindow)} of unstake window, you will
+ need to activate cooldown process again.
+
+
diff --git a/src/modules/umbrella/StakingApyItem.tsx b/src/modules/umbrella/StakingApyItem.tsx
index 769c0a11ef..b64886ea24 100644
--- a/src/modules/umbrella/StakingApyItem.tsx
+++ b/src/modules/umbrella/StakingApyItem.tsx
@@ -10,13 +10,7 @@ import invariant from 'tiny-invariant';
import { IconData, MultiIconWithTooltip } from './helpers/MultiIcon';
-export const StakingApyItem = ({
- stakeData,
- isMobile,
-}: {
- stakeData: MergedStakeData;
- isMobile?: boolean;
-}) => {
+export const StakingApyItem = ({ stakeData }: { stakeData: MergedStakeData }) => {
const { reserves } = useAppDataContext();
const icons: IconData[] = [];
@@ -68,24 +62,14 @@ export const StakingApyItem = ({
}
return (
-
-
+
+
+
{stakeData.underlyingIsStataToken ? (
Staking this asset will earn the underlying asset supply yield in addition to
@@ -145,9 +129,9 @@ export const StakingApyTooltipcontent = ({
symbol={reward.symbol}
sx={{ fontSize: '20px', mr: 1 }}
/>
- {reward.name}
+ {reward.name}
{reward.fromSupply && (
-
+
(supply)
)}
@@ -157,8 +141,8 @@ export const StakingApyTooltipcontent = ({
width="100%"
>
-
-
+
+ APY
@@ -172,18 +156,18 @@ export const StakingApyTooltipcontent = ({
mt: 1,
pt: 2,
borderTop: '1px solid',
- borderColor: 'divider',
+ borderColor: 'border-2',
}}
caption={
-
+ Total
}
width="100%"
>
-
-
+
+ APY
diff --git a/src/modules/umbrella/UmbrellaAssetsDefault.tsx b/src/modules/umbrella/UmbrellaAssetsDefault.tsx
index ac2fbe44ac..12aa821590 100644
--- a/src/modules/umbrella/UmbrellaAssetsDefault.tsx
+++ b/src/modules/umbrella/UmbrellaAssetsDefault.tsx
@@ -1,13 +1,21 @@
import { Trans } from '@lingui/macro';
-import { Box, Skeleton, Stack, Typography, useMediaQuery } from '@mui/material';
+import { Box, Paper, Skeleton, Stack, useMediaQuery, useTheme } from '@mui/material';
+import { useState } from 'react';
+import { AssetsFilterBar } from 'src/components/AssetsFilterBar';
+import { TABLE_CARDS_BELOW } from 'src/components/lists/listBreakpoints';
import { ListColumn } from 'src/components/lists/ListColumn';
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
import { ListItem } from 'src/components/lists/ListItem';
-import { ListWrapper } from 'src/components/lists/ListWrapper';
+import { NoSearchResults } from 'src/components/NoSearchResults';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Row } from 'src/components/primitives/Row';
import { FormattedStakeData, useStakeDataSummary } from 'src/hooks/stake/useUmbrellaSummary';
+import { useCoingeckoCategories } from 'src/hooks/useCoinGeckoCategories';
+import {
+ AssetCategory,
+ matchesSelectedCategories,
+} from 'src/modules/markets/utils/assetCategories';
import { useRootStore } from 'src/store/root';
import { useShallow } from 'zustand/shallow';
@@ -16,50 +24,90 @@ import { NoStakeAssets } from './NoStakeAssets';
import { StakeAssetName } from './StakeAssets/StakeAssetName';
export const UmrellaAssetsDefaultListContainer = () => {
- return (
-
- Assets to stake
-
- }
- >
-
-
- );
-};
-export const UmbrellaAssetsDefault = () => {
const [currentMarketData] = useRootStore(useShallow((store) => [store.currentMarketData]));
const { data: stakeData, loading } = useStakeDataSummary(currentMarketData);
+ const {
+ data: categoryData,
+ isLoading: isLoadingCategories,
+ error: categoriesError,
+ } = useCoingeckoCategories();
- const isTableChangedToCards = useMediaQuery('(max-width:1125px)');
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedCategories, setSelectedCategories] = useState([]);
+ const { breakpoints } = useTheme();
+ const sm = useMediaQuery(breakpoints.down('sm'));
- if (loading) {
- return isTableChangedToCards ? (
- <>
-
-
-
-
- >
- ) : (
-
-
-
-
-
-
+ const filteredAssets = stakeData?.stakeAssets
+ // Search by asset symbol
+ .filter((res) => {
+ if (!searchTerm) return true;
+ const term = searchTerm.toLowerCase().trim();
+ return res.symbol.toLowerCase().includes(term);
+ })
+ // Category filter (shares the markets page's dynamic CoinGecko categorization)
+ .filter((res) =>
+ matchesSelectedCategories(
+ res.symbol,
+ selectedCategories,
+ categoryData?.stablecoinSymbols,
+ categoryData?.ethCorrelatedSymbols
+ )
);
- }
- if (!loading && (!stakeData || stakeData.stakeAssets.length === 0)) {
- return ;
+ const noStakeAssetsConfigured = !loading && (!stakeData || stakeData.stakeAssets.length === 0);
+
+ return (
+
+
+
+
+
+
+ {noStakeAssetsConfigured ? (
+
+ ) : (
+ !loading &&
+ filteredAssets?.length === 0 && (
+ We couldn't find any assets related to your search.}
+ />
+ )
+ )}
+
+
+ );
+};
+
+export const UmbrellaAssetsDefault = ({
+ stakeAssets,
+ loading,
+}: {
+ stakeAssets: FormattedStakeData[];
+ loading: boolean;
+}) => {
+ const theme = useTheme();
+ const isTableChangedToCards = useMediaQuery(theme.breakpoints.down(TABLE_CARDS_BELOW));
+ const Loader = isTableChangedToCards
+ ? DefaultAssetListItemLoaderMobile
+ : DefaultAssetListItemLoader;
+ const Item = isTableChangedToCards ? AssetListItemMobile : AssetListItem;
+
+ // Empty states (no assets configured / no search results) are handled by the container.
+ if (!loading && stakeAssets.length === 0) {
+ return null;
}
return (
<>
{!isTableChangedToCards && (
-
+ Asset
@@ -72,14 +120,9 @@ export const UmbrellaAssetsDefault = () => {
)}
- {stakeData &&
- stakeData.stakeAssets.map((data, index) =>
- !isTableChangedToCards ? (
-
- ) : (
-
- )
- )}
+ {loading
+ ? Array.from({ length: 4 }, (_, i) => )
+ : stakeAssets.map((data, index) => )}
>
);
};
@@ -87,7 +130,7 @@ export const UmbrellaAssetsDefault = () => {
const AssetListItem = ({ stakeData }: { stakeData: FormattedStakeData }) => {
const [currentNetworkConfig] = useRootStore(useShallow((store) => [store.currentNetworkConfig]));
return (
-
+ {
@@ -124,20 +167,12 @@ const AssetListItemMobile = ({ stakeData }: { stakeData: FormattedStakeData }) =
explorerUrl={`${currentNetworkConfig.explorerLink}/address/${stakeData.tokenAddress}`}
/>
- Staking APY} captionVariant="description" mb={3}>
-
+ Staking APY} captionVariant="description" mb={3}>
+
@@ -148,9 +183,9 @@ const AssetListItemMobile = ({ stakeData }: { stakeData: FormattedStakeData }) =
const DefaultAssetListItemLoader = () => {
return (
-
+
-
+
@@ -167,16 +202,15 @@ const DefaultAssetListItemLoaderMobile = () => {
-
+ }
captionVariant="description"
align="flex-start"
diff --git a/src/modules/umbrella/UmbrellaClaimModalContent.tsx b/src/modules/umbrella/UmbrellaClaimModalContent.tsx
index 80eda03a5f..507dd8207e 100644
--- a/src/modules/umbrella/UmbrellaClaimModalContent.tsx
+++ b/src/modules/umbrella/UmbrellaClaimModalContent.tsx
@@ -130,8 +130,8 @@ export const UmbrellaClaimAllModalContent = ({ stakeData }: UmbrellaClaimAllModa
>
-
-
+
+
{reward.symbol}
@@ -140,7 +140,7 @@ export const UmbrellaClaimAllModalContent = ({ stakeData }: UmbrellaClaimAllModa
variant="helperText"
compact
symbol="USD"
- color="text.secondary"
+ color="fg-2"
/>
))}
@@ -231,8 +231,8 @@ export const UmbrellaClaimModalContent = ({ stakeData }: UmbrellaClaimModalConte
>
-
-
+
+
{reward.symbol}
@@ -241,7 +241,7 @@ export const UmbrellaClaimModalContent = ({ stakeData }: UmbrellaClaimModalConte
variant="helperText"
compact
symbol="USD"
- color="text.secondary"
+ color="fg-2"
/>
))}
diff --git a/src/modules/umbrella/UmbrellaHeader.tsx b/src/modules/umbrella/UmbrellaHeader.tsx
index 19b885315a..4797378d59 100644
--- a/src/modules/umbrella/UmbrellaHeader.tsx
+++ b/src/modules/umbrella/UmbrellaHeader.tsx
@@ -1,254 +1,86 @@
import { Trans } from '@lingui/macro';
-import { Box, Button, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
+import { PageHeader } from 'src/components/PageHeader/PageHeader';
+import { PageHeaderStat } from 'src/components/PageHeader/PageHeaderStat';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
-import { TopInfoPanel } from 'src/components/TopInfoPanel/TopInfoPanel';
import { useStakeDataSummary, useUmbrellaSummary } from 'src/hooks/stake/useUmbrellaSummary';
-import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { MarketDataType } from 'src/ui-config/marketsConfig';
-import { GENERAL } from 'src/utils/events';
-import { useShallow } from 'zustand/shallow';
-import { Link } from '../../components/primitives/Link';
-import { TopInfoPanelItem } from '../../components/TopInfoPanel/TopInfoPanelItem';
-import { MarketSwitcher } from './UmbrellaMarketSwitcher';
+type StatProps = {
+ currentMarketData: MarketDataType;
+};
+
+export const UmbrellaHeader: React.FC<{ hideStats?: boolean }> = ({ hideStats }) => (
+ Stake your Aave aTokens or underlying assets to earn rewards.}
+ >
+ {!hideStats && }
+
+);
-export const UmbrellaHeader: React.FC = () => {
- const theme = useTheme();
+const UmbrellaStats = () => {
const { currentAccount } = useWeb3Context();
- const [currentMarketData, trackEvent] = useRootStore(
- useShallow((store) => [store.currentMarketData, store.trackEvent])
- );
- // const [trackEvent, currentMarket, setCurrentMarket] = useRootStore(
- // useShallow((store) => [store.trackEvent, store.currentMarket, store.setCurrentMarket])
- // );
+ // The market is pinned to Core on the staking page (see pages/staking.page.tsx), so this reads Core.
+ const currentMarketData = useRootStore((store) => store.currentMarketData);
- const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
- const downToSM = useMediaQuery(theme.breakpoints.down('sm'));
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
+ return (
+ <>
+
+ {currentAccount ? : null}
+ >
+ );
+};
- const valueTypographyVariant = downToSM ? 'main16' : 'main21';
- const symbolsTypographyVariant = downToSM ? 'secondary16' : 'secondary21';
+// Total staked across the instance — shown whether or not a wallet is connected.
+const TotalStakedStat = ({ currentMarketData }: StatProps) => {
+ const { data: stakeData, loading } = useStakeDataSummary(currentMarketData);
return (
-
- {/* */}
-
- {/* */}
-
- Staking
-
-
-
-
-
-
- Umbrella is the upgraded version of the Safety Module. Manage your previously staked
- assets
- {' '}
-
- here.
-
-
-
-
- Stake your Aave aTokens or underlying assets to earn rewards. In case of a shortfall
- event, your stake may be slashed to cover the deficit.
- {' '}
-
- trackEvent(GENERAL.EXTERNAL_LINK, {
- Link: 'Staking Risks',
- })
- }
- >
- Learn more about the risks.
-
-
-
- }
- >
- {currentAccount ? (
-
- ) : (
-
- )}
-
+ Total Staked} loading={loading}>
+
+
);
};
-const UmbrellaHeaderUserDetails = ({
- currentMarketData,
- valueTypographyVariant,
- symbolsTypographyVariant,
-}: {
- currentMarketData: MarketDataType;
- valueTypographyVariant: 'main16' | 'main21';
- symbolsTypographyVariant: 'secondary16' | 'secondary21';
-}) => {
- const theme = useTheme();
+// Connected-only stats. Kept separate so `useUmbrellaSummary` (user-specific) is gated to the
+// connected branch rather than run for logged-out visitors.
+const UmbrellaUserStats = ({ currentMarketData }: StatProps) => {
const { data: stakedDataWithTokenBalances, loading: isLoadingStakedDataWithTokenBalances } =
useUmbrellaSummary(currentMarketData);
- const { data: stakeData, loading } = useStakeDataSummary(currentMarketData);
- const { openUmbrellaClaimAll } = useModalContext();
const totalUSDAggregateStaked = stakedDataWithTokenBalances?.aggregatedTotalStakedUSD;
const weightedAverageApy = stakedDataWithTokenBalances?.weightedAverageApy;
- const userRewardsUsd = stakedDataWithTokenBalances?.stakeData.reduce((acc, stake) => {
- const totalAvailableToClaim = stake.formattedRewards.reduce(
- (sum, reward) => sum + Number(reward.accruedUsd || '0'),
- 0
- );
- return acc + totalAvailableToClaim;
- }, 0);
-
- const userHasRewards =
- userRewardsUsd !== undefined && userRewardsUsd > 0 && !isLoadingStakedDataWithTokenBalances;
-
return (
<>
-
- Total amount staked
-
- }
- loading={loading}
- >
-
-
-
- Staked Balance
-
- }
+ Staked Balance}
loading={isLoadingStakedDataWithTokenBalances}
>
-
+
- Net APY}
- loading={isLoadingStakedDataWithTokenBalances}
- >
+ Net APY} loading={isLoadingStakedDataWithTokenBalances}>
-
- {userHasRewards && (
- Available rewards}
- loading={isLoadingStakedDataWithTokenBalances}
- hideIcon
- >
-
-
-
-
-
- openUmbrellaClaimAll()}
- sx={{ minWidth: 'unset', ml: { xs: 0, xsm: 2 } }}
- >
- Claim
-
-
-
- )}
- >
- );
-};
-
-const UmbrellaHeaderDefault = ({
- currentMarketData,
- valueTypographyVariant,
- symbolsTypographyVariant,
-}: {
- currentMarketData: MarketDataType;
- valueTypographyVariant: 'main16' | 'main21';
- symbolsTypographyVariant: 'secondary16' | 'secondary21';
-}) => {
- const theme = useTheme();
- const { data: stakeData, loading } = useStakeDataSummary(currentMarketData);
-
- return (
- <>
-
- Total amount staked
-
- }
- loading={loading}
- >
-
-
+
>
);
};
diff --git a/src/modules/umbrella/UmbrellaMarketSwitcher.tsx b/src/modules/umbrella/UmbrellaMarketSwitcher.tsx
deleted file mode 100644
index 78d352f5aa..0000000000
--- a/src/modules/umbrella/UmbrellaMarketSwitcher.tsx
+++ /dev/null
@@ -1,385 +0,0 @@
-import { ChevronDownIcon } from '@heroicons/react/outline';
-import { Trans } from '@lingui/macro';
-import {
- Box,
- BoxProps,
- ListItemText,
- MenuItem,
- SvgIcon,
- TextField,
- Tooltip,
- Typography,
- useMediaQuery,
- useTheme,
-} from '@mui/material';
-import React, { useState } from 'react';
-import { useRootStore } from 'src/store/root';
-import { BaseNetworkConfig } from 'src/ui-config/networksConfig';
-import { DASHBOARD } from 'src/utils/events';
-import {
- availableMarkets,
- CustomMarket,
- ENABLE_TESTNET,
- MarketDataType,
- marketsData,
- networkConfigs,
- STAGING_ENV,
-} from 'src/utils/marketsAndNetworksConfig';
-import { useShallow } from 'zustand/shallow';
-
-export const getMarketInfoById = (marketId: CustomMarket) => {
- const market: MarketDataType = marketsData[marketId as CustomMarket];
- const network: BaseNetworkConfig = networkConfigs[market.chainId];
- const logo = market.logo || network.networkLogoPath;
-
- return { market, logo };
-};
-
-export const getMarketHelpData = (marketName: string) => {
- const testChains = [
- 'Görli',
- 'Ropsten',
- 'Mumbai',
- 'Sepolia',
- 'Fuji',
- 'Testnet',
- 'Kovan',
- 'Rinkeby',
- ];
- const arrayName = marketName.split(' ');
- const testChainName = arrayName.filter((el) => testChains.indexOf(el) > -1);
- const marketTitle = arrayName.filter((el) => !testChainName.includes(el)).join(' ');
-
- return {
- name: marketTitle,
- testChainName: testChainName[0],
- };
-};
-
-export type Market = {
- marketTitle: string;
- networkName: string;
- networkLogo: string;
- selected?: boolean;
-};
-
-type MarketLogoProps = {
- size: number;
- logo: string;
- testChainName?: string;
- sx?: BoxProps;
-};
-
-export const MarketLogo = ({ size, logo, testChainName, sx }: MarketLogoProps) => {
- return (
-
-
-
- {testChainName && (
-
-
- {testChainName.split('')[0]}
-
-
- )}
-
- );
-};
-
-enum SelectedMarketVersion {
- V2,
- V3,
-}
-
-// TODO
-// Fetch markets that are active for umbrella.
-// Strip out any code not used for v2
-// Style to design specifications
-
-export const MarketSwitcher = () => {
- const [selectedMarketVersion] = useState(SelectedMarketVersion.V3);
- const theme = useTheme();
- const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
- const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
- const [trackEvent, currentMarket, setCurrentMarket] = useRootStore(
- useShallow((store) => [store.trackEvent, store.currentMarket, store.setCurrentMarket])
- );
-
- const isV3MarketsAvailable = availableMarkets
- .map((marketId: CustomMarket) => {
- const { market } = getMarketInfoById(marketId);
-
- return market.v3;
- })
- .some((item) => !!item);
-
- const handleMarketSelect = (e: React.ChangeEvent) => {
- trackEvent(DASHBOARD.CHANGE_MARKET, { market: e.target.value });
- setCurrentMarket(e.target.value as unknown as CustomMarket);
- };
-
- // const marketBlurbs: { [key: string]: JSX.Element } = {
- // proto_mainnet_v3: (
- // Main Ethereum market with the largest selection of assets and yield options
- // ),
- // proto_lido_v3: (
- // Optimized for efficiency and risk by supporting blue-chip collateral assets
- // ),
- // };
-
- return (
- null,
- renderValue: (marketId) => {
- const { market, logo } = getMarketInfoById(marketId as CustomMarket);
-
- return (
-
- {/* Main Row with Market Name */}
-
-
-
-
- {getMarketHelpData(market.marketTitle).name} {market.isFork ? 'Fork' : ''}
- {/* {upToLG &&
- (currentMarket === 'proto_mainnet_v3' || currentMarket === 'proto_lido_v3')
- ? 'Instance'
- : ' Market'} */}
-
-
-
- {/*
- V2
- */}
-
-
-
-
-
-
-
- {/* {marketBlurbs[currentMarket] && (
-
- {marketBlurbs[currentMarket]}
-
- )} */}
-
- );
- },
-
- sx: {
- '&.MarketSwitcher__select .MuiSelect-outlined': {
- pl: 0,
- py: 0,
- backgroundColor: 'transparent !important',
- },
- '.MuiSelect-icon': { color: '#F1F1F3' },
- },
- MenuProps: {
- anchorOrigin: {
- vertical: 'bottom',
- horizontal: 'right',
- },
- transformOrigin: {
- vertical: 'top',
- horizontal: 'right',
- },
- PaperProps: {
- style: {
- minWidth: 240,
- },
- variant: 'outlined',
- elevation: 0,
- },
- },
- }}
- >
-
-
-
- {ENABLE_TESTNET || STAGING_ENV ? 'Select Aave Testnet Market' : 'Select Aave Market'}
-
-
-
- {isV3MarketsAvailable && (
-
- {/* {
- if (value !== null) {
- setSelectedMarketVersion(value);
- }
- }}
- sx={{
- width: '100%',
- height: '36px',
- background: theme.palette.primary.main,
- border: `1px solid ${
- theme.palette.mode === 'dark' ? 'rgba(235, 235, 237, 0.12)' : '#1B2030'
- }`,
- borderRadius: '6px',
- marginTop: '16px',
- marginBottom: '12px',
- padding: '2px',
- }}
- >
-
- theme.palette.gradients.aaveGradient,
- backgroundClip: 'text',
- color: 'transparent',
- }
- : {
- color: theme.palette.mode === 'dark' ? '#0F121D' : '#FFFFFF',
- }
- }
- >
- Version 3
-
-
-
- theme.palette.gradients.aaveGradient,
- backgroundClip: 'text',
- color: 'transparent',
- }
- : {
- color: theme.palette.mode === 'dark' ? '#0F121D' : '#FFFFFF',
- }
- }
- >
- Version 2
-
-
- */}
-
- )}
- {availableMarkets.map((marketId: CustomMarket) => {
- const { market, logo } = getMarketInfoById(marketId);
- const marketNaming = getMarketHelpData(market.marketTitle);
- return (
-
- );
- })}
-
- );
-};
diff --git a/src/modules/umbrella/UmbrellaModalContent.tsx b/src/modules/umbrella/UmbrellaModalContent.tsx
index 7ff2fd4bc3..22876f56ea 100644
--- a/src/modules/umbrella/UmbrellaModalContent.tsx
+++ b/src/modules/umbrella/UmbrellaModalContent.tsx
@@ -1,11 +1,10 @@
import { USD_DECIMALS, valueToBigNumber } from '@aave/math-utils';
import { Trans } from '@lingui/macro';
-import { Box, Checkbox, Skeleton, Stack, Typography } from '@mui/material';
+import { Alert, Box, Checkbox, Skeleton, Stack, Typography } from '@mui/material';
import { parseUnits } from 'ethers/lib/utils';
import React, { useState } from 'react';
import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Row } from 'src/components/primitives/Row';
-import { Warning } from 'src/components/primitives/Warning';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
import { AssetInput } from 'src/components/transactions/AssetInput';
import { TxErrorView } from 'src/components/transactions/FlowCommons/Error';
@@ -224,10 +223,10 @@ export const UmbrellaModalContent = ({ stakeData, user, userReserve, poolReserve
/>
) : (
<>
-
+
-
+
Staking this amount will reduce your health factor and increase risk of liquidation.
-
+
-
+
>
)}
diff --git a/src/modules/umbrella/helpers/AmountAvailableItem.tsx b/src/modules/umbrella/helpers/AmountAvailableItem.tsx
index ed5a818530..99aba67b69 100644
--- a/src/modules/umbrella/helpers/AmountAvailableItem.tsx
+++ b/src/modules/umbrella/helpers/AmountAvailableItem.tsx
@@ -27,12 +27,12 @@ export const AmountAvailableItem = ({
aToken={aToken}
waToken={waToken}
/>
- {name}
+ {name}
}
width="100%"
>
-
+
);
};
diff --git a/src/modules/umbrella/helpers/ApyTooltip.tsx b/src/modules/umbrella/helpers/ApyTooltip.tsx
index 7ffc3c9a72..8b79c5205a 100644
--- a/src/modules/umbrella/helpers/ApyTooltip.tsx
+++ b/src/modules/umbrella/helpers/ApyTooltip.tsx
@@ -1,10 +1,11 @@
import { Trans } from '@lingui/macro';
+import { TypographyProps } from '@mui/material';
import { Link } from 'src/components/primitives/Link';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
-export const ApyTooltip = () => {
+export const ApyTooltip = ({ variant }: { variant?: TypographyProps['variant'] }) => {
return (
- APY}>
+ APY} variant={variant}>
<>
Reward APY adjusts with total staked amount, following a curve that targets optimal
diff --git a/src/modules/umbrella/helpers/Helpers.tsx b/src/modules/umbrella/helpers/Helpers.tsx
index 831ffa4015..6e4a2c28ce 100644
--- a/src/modules/umbrella/helpers/Helpers.tsx
+++ b/src/modules/umbrella/helpers/Helpers.tsx
@@ -44,7 +44,7 @@ export const UmbrellaAssetBreakdown = ({
flexDirection: 'column',
}}
>
-
+
Participating in staking {symbol} gives annualized rewards. Your wallet balance is the
sum of your aTokens and underlying assets. The breakdown to stake is below
@@ -70,7 +70,7 @@ export const UmbrellaAssetBreakdown = ({
@@ -92,7 +92,7 @@ export const UmbrellaAssetBreakdown = ({
@@ -115,12 +115,12 @@ export const UmbrellaAssetBreakdown = ({
- ({ pt: 1, mt: 1 })}>
+ Total} height={32}>
diff --git a/src/modules/umbrella/helpers/SharesTooltip.tsx b/src/modules/umbrella/helpers/SharesTooltip.tsx
index 1ee186dd4a..2e5849b8fc 100644
--- a/src/modules/umbrella/helpers/SharesTooltip.tsx
+++ b/src/modules/umbrella/helpers/SharesTooltip.tsx
@@ -1,9 +1,10 @@
import { Trans } from '@lingui/macro';
+import { TypographyProps } from '@mui/material';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
-export const SharesTooltip = () => {
+export const SharesTooltip = ({ variant }: { variant?: TypographyProps['variant'] }) => {
return (
- Shares}>
+ Shares} variant={variant}>
<>
Shares are Umbrella Stake Tokens you receive when staking. They represent your ownership
diff --git a/src/modules/umbrella/helpers/StakedUnderlyingTooltip.tsx b/src/modules/umbrella/helpers/StakedUnderlyingTooltip.tsx
index 3449c3c03e..284e245c07 100644
--- a/src/modules/umbrella/helpers/StakedUnderlyingTooltip.tsx
+++ b/src/modules/umbrella/helpers/StakedUnderlyingTooltip.tsx
@@ -1,9 +1,10 @@
import { Trans } from '@lingui/macro';
+import { TypographyProps } from '@mui/material';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
-export const StakedUnderlyingTooltip = () => {
+export const StakedUnderlyingTooltip = ({ variant }: { variant?: TypographyProps['variant'] }) => {
return (
- Staked Underlying}>
+ Staked Underlying} variant={variant}>
<>
Total amount of underlying assets staked. This number represents the combined sum of your
diff --git a/src/modules/umbrella/helpers/StakingDropdown.tsx b/src/modules/umbrella/helpers/StakingDropdown.tsx
index 803f60992f..380ed2eb25 100644
--- a/src/modules/umbrella/helpers/StakingDropdown.tsx
+++ b/src/modules/umbrella/helpers/StakingDropdown.tsx
@@ -3,7 +3,7 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime';
import AddOutlinedIcon from '@mui/icons-material/AddOutlined';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import StartIcon from '@mui/icons-material/Start';
-import { Button, Stack, useMediaQuery, useTheme } from '@mui/material';
+import { Button, Stack, useTheme } from '@mui/material';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
@@ -33,14 +33,17 @@ const StyledMenuItem = styled(MenuItem)({
},
});
-export const StakingDropdown = ({ stakeData }: { stakeData: MergedStakeData }) => {
+export const StakingDropdown = ({
+ stakeData,
+ fullWidth,
+}: {
+ stakeData: MergedStakeData;
+ fullWidth?: boolean;
+}) => {
const { openUmbrella, openUmbrellaStakeCooldown, openUmbrellaUnstake, openUmbrellaClaim } =
useModalContext();
const trackEvent = useRootStore((store) => store.trackEvent);
const now = useCurrentTimestamp(1);
- const { breakpoints } = useTheme();
-
- const isMobile = useMediaQuery(breakpoints.down('lg'));
const endOfCooldown = stakeData?.cooldownData.endOfCooldown || 0;
const unstakeWindow = stakeData?.cooldownData.withdrawalWindow || 0;
@@ -80,8 +83,9 @@ export const StakingDropdown = ({ stakeData }: { stakeData: MergedStakeData }) =
{!hasStakeTokenBalance && !hasUnclaimedRewards ? (
{
trackEvent(STAKE.STAKE_TOKEN, {
action: STAKE.OPEN_STAKE_MODAL,
@@ -104,7 +108,7 @@ export const StakingDropdown = ({ stakeData }: { stakeData: MergedStakeData }) =
<>
@@ -153,7 +157,7 @@ export const StakingDropdown = ({ stakeData }: { stakeData: MergedStakeData }) =
alignItems="center"
justifyContent="space-between"
>
-
+ Cooling down
@@ -191,7 +195,7 @@ export const StakingDropdown = ({ stakeData }: { stakeData: MergedStakeData }) =
alignItems="center"
justifyContent="space-between"
>
-
+ Withdraw
diff --git a/src/ui-config/funkit/aaveTheme.ts b/src/ui-config/funkit/aaveTheme.ts
index 55bf0851bd..37c266cd3e 100644
--- a/src/ui-config/funkit/aaveTheme.ts
+++ b/src/ui-config/funkit/aaveTheme.ts
@@ -1,57 +1,53 @@
import { type ThemeOptions, darkTheme, lightTheme } from '@funkit/connect';
+import { alpha } from '@mui/material';
+import { type FigmaColorName, pickFigma } from 'src/utils/figmaColors';
+import { FONT } from 'src/utils/theme';
/**
- * Aave funkit theme, ported *almost* verbatim from the funkit playground's customer theme
- * (`funkit:apps/with-next/themes/aave.ts`) — the Figma-derived theme the fun team
- * maintains for Aave (Customer-Themes file).
+ * Aave funkit theme, mapped onto the interface's own design tokens so the checkout reads as part
+ * of the app rather than the fun team's stock customer theme. Colours resolve from
+ * `figmaLight`/`figmaDark` as concrete per-scheme values — funkit builds its theme outside React
+ * and swaps schemes itself (see FunkitCheckout's `toggleTheme`), so it can't take CSS vars.
*/
-
-// The canonical aave theme (sizings included) was designed and QA'd against the
-// with-next playground's body font — a system-first stack that renders SF Pro on
-// macOS. The interface's body font is Inter, whose taller metrics make the same
-// sizings look heavier/cramped (and clip in tight line-heights), so instead of
-// `customFontFamily: 'inherit'` we pin the modal to the playground's exact stack
-// to match the reference rendering pixel-for-pixel. If design wants the modal in
-// Inter (the app font), the sizings below need an Inter-specific re-tune first.
-const customFontFamily =
- "-apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', 'Roboto', sans-serif";
-
const customFontSizings: ThemeOptions['customFontSizings'] = {
- // Bump fontSize by 1px, keep lineHeight
- '10': { fontSize: '11px', lineHeight: '16px' },
- '12': { fontSize: '13px', lineHeight: '15px' },
- '13': { fontSize: '14px', lineHeight: '19px' },
- '14': { fontSize: '15px', lineHeight: '19px' },
- '16': { fontSize: '17px', lineHeight: '21px' },
- '18': { fontSize: '19px', lineHeight: '25px' },
- '20': { fontSize: '21px', lineHeight: '21px' },
- '21': { fontSize: '22px', lineHeight: '22px' },
- '40': { fontSize: '41px', lineHeight: '49px' },
- '57': { fontSize: '58px', lineHeight: '69px' },
- modalTopbarTitle: { fontSize: '21px', lineHeight: '24px' },
- modalTopbarSubtitle: { fontSize: '12px', lineHeight: '19px' },
- modalBottomBarButtonText: { fontSize: '13px', lineHeight: '16px' },
+ '10': { fontSize: '10px', lineHeight: '12px' },
+ '12': { fontSize: '12px', lineHeight: '16px' },
+ '13': { fontSize: '13px', lineHeight: '18px' },
+ '14': { fontSize: '14px', lineHeight: '20px' },
+ '16': { fontSize: '16px', lineHeight: '24px' },
+ '18': { fontSize: '18px', lineHeight: '22px' },
+ '20': { fontSize: '20px', lineHeight: '24px' },
+ '21': { fontSize: '21px', lineHeight: '28px' },
+ '40': { fontSize: '40px', lineHeight: '48px' },
+ '57': { fontSize: '57px', lineHeight: '68px' },
+ modalTopbarTitle: { fontSize: '16px', lineHeight: '24px' },
+ modalTopbarSubtitle: { fontSize: '12px', lineHeight: '16px' },
+ modalBottomBarButtonText: { fontSize: '14px', lineHeight: '20px' },
};
const customBorderRadiuses = {
- modal: '4px',
- modalMobile: '4px',
- modalActionButton: '4px',
- modalActionButtonMobile: '4px',
- connectButton: '4px',
- qrCode: '4px',
- tooltip: '4px',
- skeleton: '4px',
- actionButton: '4px',
- actionButtonInner: '3px',
- menuButton: '4px',
- summaryBox: '4px',
- dropdownItem: '4px',
+ modal: '0.75rem',
+ modalMobile: '0.75rem',
+ modalActionButton: '0.5rem',
+ modalActionButtonMobile: '0.5rem',
+ connectButton: '0.5rem',
+ qrCode: '0.5rem',
+ tooltip: '0.5rem',
+ skeleton: '0.5rem',
+ actionButton: '0.5rem',
+ actionButtonInner: '0.375rem',
+ menuButton: '0.5rem',
+ summaryBox: '0.75rem',
+ dropdownItem: '0.5rem',
youPayYouReceive: '50px',
- inputAmountSwitcher: '4px',
- dropdown: '4px',
+ inputAmountSwitcher: '0.5rem',
+ dropdown: '0.75rem',
};
+// The app's contained button hovers by compositing a translucent `button-hover-primary` overlay
+// over its fg-1 fill; funkit takes a flat colour, so these are that overlay already composited.
+const primaryFillHover = { light: '#292929', dark: '#d6d6d6' };
+
// Soft green-yellow gradient composited as a translucent overlay on top of
// the modal background during the post-checkout success state. Values
// lifted from Figma node 10852:39855 (Customer-Themes file); the solid
@@ -63,148 +59,127 @@ const aaveCheckoutCompleteGradient = [
'linear-gradient(-7.43deg, rgba(102,204,0,0) 57.00%, rgba(102,204,0,0.1) 97.77%)',
].join(', ');
-const darkThemeColors = {
- primaryText: '#F1F1F3',
- secondaryText: '#A5A8B5',
- tertiaryText: '#A5A8B5',
- lightStroke: '#393C4D',
- mediumStroke: '#393C4D',
- heavyStroke: '#414558',
- modalBackground: '#2A2E40',
- actionColor: '#FFFFFF',
- offBackground: '#393D4F',
- offBackgroundInverse: '#A5A8B5',
- secondaryBackground: '#42475C',
+const customDimensions = {
+ modalBottomBarButtonHeight: '44px',
+ modalTopBarHeight: '72px',
};
-const darkThemeObject = darkTheme({
- customFontFamily,
- customColors: {
- ...darkThemeColors,
- modalBackdrop: 'rgba(0, 0, 0, 0.6)',
+const customSpacings = {
+ cryptoCashToggleTabPaddingY: '12px',
+ modalTopBarVerticalTextSpacing: '6px',
+};
+
+/** The seven values that genuinely differ between the two schemes. */
+type SchemeOverrides = {
+ modalBackground: FigmaColorName;
+ tertiaryFill: FigmaColorName;
+ tertiaryFillHover: FigmaColorName;
+ tertiaryFillDisabled: FigmaColorName;
+ activeTab: FigmaColorName;
+ cryptoCashToggle: FigmaColorName;
+ primaryFillHover: string;
+};
+
+const buildColors = (mode: 'light' | 'dark', o: SchemeOverrides) => {
+ const t = pickFigma(mode);
+ const stroke = t['border-1'];
+ const offBackground = t['bg-3'];
+ const secondaryBackground = t['bg-4'];
+
+ return {
+ primaryText: t['fg-1'],
+ secondaryText: t['fg-2'],
+ tertiaryText: t['fg-3'],
+ lightStroke: t['border-0'],
+ mediumStroke: stroke,
+ heavyStroke: t['border-2'],
+ modalBackground: t[o.modalBackground],
+ actionColor: t['fg-1'],
+ offBackground,
+ offBackgroundInverse: t['fg-2'],
+ secondaryBackground,
+
+ modalBackdrop: 'rgba(0, 0, 0, 0.32)',
modalBackgroundCheckoutComplete: aaveCheckoutCompleteGradient,
- modalHeaderDivider: darkThemeColors.mediumStroke,
- modalFooterDivider: darkThemeColors.mediumStroke,
- modalBorder: darkThemeColors.lightStroke,
-
- buttonTextPrimary: '#2A2E40',
- buttonTextHover: '#2A2E40',
- buttonTextDisabled: 'rgba(42, 46, 64, 0.9)',
-
- buttonBackground: darkThemeColors.actionColor,
- buttonBackgroundHover: '#D2D4DB',
- buttonBackgroundPressed: '#D2D4DB',
- buttonBackgroundDisabled: 'rgba(255, 255, 255, 0.5)',
-
- buttonTextTertiary: '#F1F1F3',
- buttonTextDisabledTertiary: 'rgba(241, 241, 243, 0.5)',
- buttonBackgroundTertiary: darkThemeColors.offBackground,
- buttonBackgroundHoverTertiary: darkThemeColors.secondaryBackground,
- buttonBackgroundDisabledTertiary: 'rgba(57, 61, 79, 0.5)',
-
- youPayYouReceiveBorder: darkThemeColors.mediumStroke,
- youPayYouReceiveBackground: darkThemeColors.modalBackground,
- inputAmountQuickOptionBaseBackground: darkThemeColors.offBackground,
- inputAmountQuickOptionHoverBackground: darkThemeColors.secondaryBackground,
- focusedOptionBorder: darkThemeColors.actionColor,
- modalTopbarIcon: darkThemeColors.secondaryText,
- modalTopbarIconBackgroundHover: darkThemeColors.secondaryBackground,
- modalTopbarIconBackgroundPressed: darkThemeColors.secondaryBackground,
- buttonIconBackgroundHover: darkThemeColors.secondaryBackground,
- buttonBorderFocusedTertiary: darkThemeColors.mediumStroke,
- menuItemBackground: darkThemeColors.offBackground,
- copyButtonBackgroundHover: darkThemeColors.secondaryBackground,
- copyButtonBackgroundActive: darkThemeColors.secondaryBackground,
- funFeatureListBackgroundHover: darkThemeColors.secondaryBackground,
- activeTabBackground: darkThemeColors.secondaryBackground,
- activeTabBorderColor: 'rgba(165, 168, 181, 0.1)',
- cryptoCashToggleBackground: darkThemeColors.offBackground,
- generalBorder: darkThemeColors.mediumStroke,
- inputBorderHover: darkThemeColors.offBackground,
- },
+ modalHeaderDivider: stroke,
+ modalFooterDivider: stroke,
+ modalBorder: stroke,
+
+ buttonTextPrimary: t['bg-1'],
+ buttonTextHover: t['bg-1'],
+ buttonTextDisabled: t['bg-1'],
+
+ buttonBackground: t['fg-1'],
+ buttonBackgroundHover: o.primaryFillHover,
+ buttonBackgroundPressed: o.primaryFillHover,
+ buttonBackgroundDisabled: alpha(t['fg-1'], 0.5),
+
+ buttonTextTertiary: t['fg-1'],
+ buttonTextDisabledTertiary: t['fg-3'],
+ buttonBackgroundTertiary: t[o.tertiaryFill],
+ buttonBackgroundHoverTertiary: t[o.tertiaryFillHover],
+ buttonBackgroundDisabledTertiary: t[o.tertiaryFillDisabled],
+
+ youPayYouReceiveBorder: stroke,
+ youPayYouReceiveBackground: t[o.modalBackground],
+ inputAmountQuickOptionBaseBackground: offBackground,
+ inputAmountQuickOptionHoverBackground: secondaryBackground,
+ focusedOptionBorder: t['fg-1'],
+ modalTopbarIcon: t['fg-2'],
+ modalTopbarIconBackgroundHover: secondaryBackground,
+ modalTopbarIconBackgroundPressed: secondaryBackground,
+ buttonIconBackgroundHover: secondaryBackground,
+ buttonBorderFocusedTertiary: stroke,
+ menuItemBackground: offBackground,
+ copyButtonBackgroundHover: secondaryBackground,
+ copyButtonBackgroundActive: secondaryBackground,
+ funFeatureListBackgroundHover: secondaryBackground,
+ activeTabBackground: t[o.activeTab],
+ activeTabBorderColor: stroke,
+ cryptoCashToggleBackground: t[o.cryptoCashToggle],
+ generalBorder: stroke,
+ inputBorderHover: offBackground,
+ };
+};
+
+const darkThemeObject = darkTheme({
+ customFontFamily: FONT,
+ customColors: buildColors('dark', {
+ modalBackground: 'bg-2',
+ tertiaryFill: 'bg-4',
+ tertiaryFillHover: 'bg-5',
+ tertiaryFillDisabled: 'bg-3',
+ activeTab: 'bg-4',
+ cryptoCashToggle: 'bg-3',
+ primaryFillHover: primaryFillHover.dark,
+ }),
customFontSizings,
customBorderRadiuses,
- customDimensions: {
- modalBottomBarButtonHeight: '40px',
- modalTopBarHeight: '76px',
- },
- customSpacings: {
- cryptoCashToggleTabPaddingY: '12px',
- modalTopBarVerticalTextSpacing: '6px',
- },
+ customDimensions,
+ customSpacings,
overlayBlur: 'none',
});
-const lightThemeColors = {
- primaryText: '#313547',
- secondaryText: '#636779',
- tertiaryText: '#636779',
- lightStroke: '#EAEBEF',
- mediumStroke: '#EAEBEF',
- heavyStroke: '#E8E9ED',
- modalBackground: '#FFFFFF',
- actionColor: '#393D4F',
- offBackground: '#F7F7F9',
- offBackgroundInverse: '#393D4F',
- secondaryBackground: '#EAEBEF',
-};
-
const lightThemeObject = lightTheme({
- customFontFamily,
- customColors: {
- ...lightThemeColors,
- modalBackdrop: 'rgba(0, 0, 0, 0.6)',
- modalBackgroundCheckoutComplete: aaveCheckoutCompleteGradient,
- modalHeaderDivider: lightThemeColors.mediumStroke,
- modalFooterDivider: lightThemeColors.mediumStroke,
- modalBorder: '#FFFFFF',
-
- buttonTextPrimary: '#FFFFFF',
- buttonTextHover: '#FFFFFF',
- buttonTextDisabled: 'rgba(255, 255, 255, 1)',
-
- buttonBackground: lightThemeColors.actionColor,
- buttonBackgroundHover: '#2A2E40',
- buttonBackgroundPressed: '#2A2E40',
- buttonBackgroundDisabled: 'rgba(57, 61, 79, 0.45)',
-
- buttonTextTertiary: '#636779',
- buttonTextDisabledTertiary: 'rgba(99, 103, 121, 0.5)',
- buttonBackgroundTertiary: lightThemeColors.offBackground,
- buttonBackgroundHoverTertiary: lightThemeColors.secondaryBackground,
- buttonBackgroundDisabledTertiary: 'rgba(247, 247, 249, 0.5)',
-
- youPayYouReceiveBorder: lightThemeColors.mediumStroke,
- youPayYouReceiveBackground: lightThemeColors.modalBackground,
- inputAmountQuickOptionBaseBackground: lightThemeColors.offBackground,
- inputAmountQuickOptionHoverBackground: lightThemeColors.secondaryBackground,
- focusedOptionBorder: lightThemeColors.actionColor,
- modalTopbarIcon: lightThemeColors.secondaryText,
- modalTopbarIconBackgroundHover: lightThemeColors.secondaryBackground,
- modalTopbarIconBackgroundPressed: lightThemeColors.secondaryBackground,
- buttonIconBackgroundHover: lightThemeColors.secondaryBackground,
- buttonBorderFocusedTertiary: lightThemeColors.mediumStroke,
- menuItemBackground: lightThemeColors.offBackground,
- copyButtonBackgroundHover: lightThemeColors.secondaryBackground,
- copyButtonBackgroundActive: lightThemeColors.secondaryBackground,
- funFeatureListBackgroundHover: lightThemeColors.secondaryBackground,
- activeTabBackground: lightThemeColors.modalBackground,
- activeTabBorderColor: lightThemeColors.heavyStroke,
- cryptoCashToggleBackground: lightThemeColors.offBackground,
- },
+ customFontFamily: FONT,
+ customColors: buildColors('light', {
+ modalBackground: 'bg-1',
+ tertiaryFill: 'bg-3',
+ tertiaryFillHover: 'bg-4',
+ tertiaryFillDisabled: 'bg-2',
+ activeTab: 'bg-3',
+ cryptoCashToggle: 'bg-4',
+ primaryFillHover: primaryFillHover.light,
+ }),
customFontSizings,
customBorderRadiuses,
customShadows: {
- dialog: 'rgba(0, 0, 0, 0.05) 0px 2px 1px, rgba(0, 0, 0, 0.25) 0px 0px 1px;',
- },
- customDimensions: {
- modalBottomBarButtonHeight: '40px',
- modalTopBarHeight: '76px',
- },
- customSpacings: {
- cryptoCashToggleTabPaddingY: '12px',
- modalTopBarVerticalTextSpacing: '6px',
+ dialog: `0 0 0 1px ${pickFigma('light')['border-1']}, 0 4px 16px 0 ${
+ pickFigma('light')['shadow-medium']
+ }`,
},
+ customDimensions,
+ customSpacings,
overlayBlur: 'none',
});
diff --git a/src/utils/buttonStyles.ts b/src/utils/buttonStyles.ts
new file mode 100644
index 0000000000..f358c63090
--- /dev/null
+++ b/src/utils/buttonStyles.ts
@@ -0,0 +1,60 @@
+import { SxProps, Theme } from '@mui/material';
+
+/**
+ * Icon-only button styling: a square button — no min-width, equal 0.25rem padding on all
+ * sides, and a fixed 0.5rem radius regardless of button size. Compose it in `sx` on top of
+ * any Button variant/size (it only adjusts sizing):
+ *
+ *
+ *
+ *
+ */
+export const iconButtonSx = {
+ minWidth: 0,
+ p: '0.25rem',
+ // Square: match the width to the button's own height (set by its size slot) so it's a square
+ // whatever the icon's width — otherwise a medium button (36px tall) with an 18px icon renders
+ // as a tall rectangle.
+ aspectRatio: '1',
+ // Fixed radius even at size="small" (whose slot would otherwise apply 0.375rem); sx wins
+ // over the theme's per-size styleOverride.
+ borderRadius: '0.5rem',
+ // `satisfies` (not a `SxProps` annotation) keeps the narrow literal type so this can also be
+ // composed inside an `sx` array — e.g. `sx={[iconButtonSx, { ... }]}`.
+} satisfies SxProps;
+
+/** Row action button in the sGHO / stkGHO deposit rows: full-width on mobile, fixed from `xsm`. */
+export const depositRowActionSx = {
+ minWidth: { xs: '140px', xsm: '96px' },
+ height: '36px',
+ width: { xs: '100%', xsm: 'auto' },
+} satisfies SxProps;
+
+/** Row action button in the staking panels: full-width on mobile, fixed from `xsm`. */
+export const stakePanelActionSx = {
+ minWidth: '96px',
+ mb: { xs: 6, xsm: 0 },
+ width: { xs: '100%', xsm: 'auto' },
+} satisfies SxProps;
+
+/** Outer shell of an sGHO / stkGHO deposit row: identity on the left, actions on the right. */
+export const depositRowSx = {
+ display: 'flex',
+ alignItems: { xs: 'stretch', xsm: 'center' },
+ justifyContent: 'space-between',
+ flexDirection: { xs: 'column', xsm: 'row' },
+ gap: 4,
+ borderRadius: { xs: '8px', xsm: '6px' },
+ p: 4,
+ mb: 6,
+} satisfies SxProps;
+
+/** The APR block + action buttons of a deposit row; the buttons take their own line on mobile. */
+export const depositRowActionsSx = {
+ display: 'flex',
+ flexDirection: { xs: 'column', xsm: 'row' },
+ alignItems: { xs: 'stretch', xsm: 'center' },
+ justifyContent: { xs: 'flex-start', xsm: 'flex-end' },
+ gap: { xs: '1rem', xsm: '0.75rem' },
+ flexShrink: 0,
+} satisfies SxProps;
diff --git a/src/utils/cardStyles.ts b/src/utils/cardStyles.ts
new file mode 100644
index 0000000000..d7656bae4a
--- /dev/null
+++ b/src/utils/cardStyles.ts
@@ -0,0 +1,32 @@
+import { SxProps, Theme } from '@mui/material';
+
+/**
+ * Standard padding for a `Paper variant="card"` panel: a tighter top than sides, and 16px sides on
+ * mobile stepping to 24px from `xsm`. Kept out of the `card` variant itself because several cards
+ * pad an inner Box instead and would double up.
+ *
+ *
+ */
+export const cardPaddingSx: SxProps = {
+ pt: 4,
+ pb: { xs: 4, xsm: 6 },
+ px: { xs: 4, xsm: 6 },
+};
+
+/**
+ * Card heading row: reserves the header band's height so a panel's header does not change height
+ * depending on whether an action button shares the row.
+ */
+export const CARD_HEADING_HEIGHT = '36px';
+
+export const cardHeadingSx = {
+ minHeight: CARD_HEADING_HEIGHT,
+ display: 'flex',
+ alignItems: 'center',
+} satisfies SxProps;
+
+/** Stat label inside the staking panels: smaller and muted from `xsm`, larger and full-ink below. */
+export const panelStatLabelSx: SxProps = {
+ typography: { xs: 'description', xsm: 'subheader2' },
+ color: { xs: 'fg-1', xsm: 'fg-2' },
+};
diff --git a/src/utils/colorToP3.ts b/src/utils/colorToP3.ts
new file mode 100644
index 0000000000..f53dbe8aef
--- /dev/null
+++ b/src/utils/colorToP3.ts
@@ -0,0 +1,18 @@
+import { decomposeColor } from '@mui/material/styles';
+
+/**
+ * Convert an sRGB color string (hex, `rgb()`, or `rgba()`) to its Display-P3 equivalent
+ * using the same naive channel mapping the Figma export uses (channels / 255, relabeled as
+ * `color(display-p3 …)`). This matches the design source's P3 values and, on wide-gamut
+ * displays, renders saturated colors richer while leaving near-grays visually unchanged.
+ *
+ * Used to generate the `@supports (color-gamut: p3)` override layer for the theme's CSS
+ * variables. Non-color / already-`color()` inputs are returned unchanged.
+ */
+export const colorToP3 = (color: string): string => {
+ if (!color.startsWith('#') && !color.startsWith('rgb')) return color;
+ // decomposeColor parses #nnn / #nnnnnn / rgb() / rgba() → { values: [r, g, b, a?] } (r,g,b 0-255).
+ const [r, g, b, a] = decomposeColor(color).values;
+ const channels = `${r / 255} ${g / 255} ${b / 255}`;
+ return a === undefined ? `color(display-p3 ${channels})` : `color(display-p3 ${channels} / ${a})`;
+};
diff --git a/src/utils/figmaColors.ts b/src/utils/figmaColors.ts
new file mode 100644
index 0000000000..f3cd87b447
--- /dev/null
+++ b/src/utils/figmaColors.ts
@@ -0,0 +1,312 @@
+/**
+ * Figma color tokens — the SINGLE SOURCE OF TRUTH for every color value in the app (light +
+ * dark). The theme flattens these onto the MUI palette root, so each becomes a `--mui-palette-*`
+ * CSS var (Display-P3 + sRGB fallback). Consume them as bare token strings in `sx`
+ * (`sx={{ bgcolor: 'bg-1' }}`) or via `figVars` outside `sx` — never hand-write hex in components.
+ */
+export const figmaLight = {
+ 'bg-max': '#f0f0f0',
+ 'bg-1': '#fafafa',
+ 'bg-2': '#fcfcfc',
+ 'bg-3': '#ffffff',
+ 'bg-4': '#f2f2f2',
+ 'bg-5': '#f1f1f1',
+ 'bg-6': '#ebebeb',
+ 'border-0': 'rgba(0, 0, 0, 0.06)',
+ 'border-1': 'rgba(0, 0, 0, 0.08)',
+ 'border-2': 'rgba(0, 0, 0, 0.1)',
+ 'border-opaque': '#e5e6e6',
+ 'fg-max': '#000000',
+ 'fg-1': '#000000',
+ 'fg-2': '#666666',
+ 'fg-3': '#7d7d7d',
+ 'fg-4': '#a8a8a8',
+ 'fg-5': '#b3b3b3',
+ // Muted icon grey (search, sortable-column chevrons, …). Deliberately mode-agnostic — the same
+ // value in both maps — unlike the fg-* ramp steps.
+ 'fg-icon': '#A8A8A8',
+ selected: 'rgba(46, 15, 15, 0.04)',
+ 'blue-1': '#1a88f8',
+ 'blue-2': '#48abff',
+ 'blue-3': '#a9e7ff',
+ 'yellow-1': '#ffb200',
+ 'yellow-2': '#ffcc00',
+ 'yellow-3': '#f6d551',
+ 'red-1': '#f24900',
+ 'red-2': '#ff8947',
+ 'red-3': '#ffc693',
+ 'purple-1': '#9391f7',
+ 'purple-2': '#bcbbff',
+ 'purple-3': '#e2e0ff',
+ 'green-1': '#1f807b',
+ 'green-2': '#63bbb6',
+ 'green-3': '#9debe7',
+ 'cyan-1': '#6bcef5',
+ 'cyan-2': '#b5e7fa',
+ 'cyan-3': '#dff6ff',
+ 'navy-1': '#1c4886',
+ 'navy-2': '#6188c0',
+ 'navy-3': '#b0d3ff',
+ 'shadow-low': 'rgba(0, 0, 0, 0.03)',
+ 'shadow-medium': 'rgba(0, 0, 0, 0.05)',
+ 'shadow-high': 'rgba(0, 0, 0, 0.07)',
+ 'shadow-strong': 'rgba(0, 0, 0, 0.11)',
+ 'shadow-stroke-1': 'rgba(0, 0, 0, 0.06)',
+ 'shadow-stroke-2': 'rgba(0, 0, 0, 0.08)',
+ ethereum: '#25292e',
+ focus: 'rgba(26, 136, 248, 0.2)',
+ scrim: 'rgba(247, 246, 246, 0.8)',
+ // Data-viz categorical palette (17 hues, red → pink).
+ 'data-red': '#FF4760',
+ 'data-coral': '#FF513D',
+ 'data-orange': '#FF7029',
+ 'data-honey': '#FF8C00',
+ 'data-yellow': '#DBA400',
+ 'data-pear': '#CCAB00',
+ 'data-light-green': '#22CE80',
+ 'data-green': '#00BD68',
+ 'data-matcha': '#89BE2D',
+ 'data-seafoam': '#00B89F',
+ 'data-teal': '#05B4C7',
+ 'data-lagoon': '#12B4D9',
+ 'data-blue': '#38B0F5',
+ 'data-azure': '#4797FF',
+ 'data-purple': '#837AFF',
+ 'data-lavender': '#C061FF',
+ 'data-pink': '#EB47CF',
+ 'button-hover': 'rgba(0, 0, 0, 0.025)',
+ 'overlay-hover': 'rgba(0, 0, 0, 0.08)',
+ 'overlay-hover-subtle': 'rgba(0, 0, 0, 0.04)',
+ 'data-green-gho': '#5dff93',
+ // Gold for the favourited market star (mode-agnostic; Figma color(display-p3 1 0.7 0)).
+ 'favourite-star': '#FFB300',
+ // Alert "danger" severity red (icon + gradient); distinct from the muted error-* palette.
+ danger: '#DC2626',
+ // sGHO markets-banner gradient: a data-green wash at 6% fading to the banner's own surface.
+ 'sgho-banner-green': 'rgba(50, 201, 88, 0.06)',
+ 'chain-testnet': '#8594ab',
+ 'chain-ethereum': '#25292e',
+ 'chain-polygon': '#8347e5',
+ 'chain-base': '#0052ff',
+ 'chain-optimism': '#e84142',
+ 'chain-lens': '#36a136',
+ 'chain-arbitrum': '#28a0f0',
+ 'chain-blast': '#ffc700',
+ 'chain-scroll': '#f8cf6e',
+ 'chain-worldchain': '#ff9d00',
+ 'chain-zksync': '#8c8dfe',
+ bone: '#f6f7f4',
+ // Opaque hover fill for the Select trigger. A SINGLE per-mode token rather than a base +
+ // `darkScheme()` override, so it resolves to the NEAREST color scheme — the dev showcase's local
+ // toggle works even when the app's global scheme differs (the dark selector matches any ancestor,
+ // including , so a two-token swap leaks across a nested scheme boundary).
+ 'bg-4-hover': '#f6f7f4',
+ // --- semantic tokens promoted from theme-file literals (SoT) ---
+ 'secondary-main': '#FF607B',
+ 'secondary-light': '#FF607B',
+ 'secondary-dark': '#B34356',
+ 'error-light': '#D26666',
+ 'error-dark': '#BC0000',
+ 'error-text': '#4F1919',
+ 'error-bg': '#F9EBEB',
+ 'warning-light': '#FFCE00',
+ 'warning-dark': '#C67F15',
+ 'warning-text': '#63400A',
+ 'warning-bg': '#FEF5E8',
+ 'info-light': '#0062D2',
+ 'info-dark': '#002754',
+ 'info-text': '#002754',
+ 'info-bg': '#E5EFFB',
+ 'success-light': '#90FF95',
+ 'success-dark': '#318435',
+ 'success-text': '#1C4B1E',
+ 'success-bg': '#ECF8ED',
+ 'disabled-fg': '#BBBECA',
+ 'disabled-bg': '#EAEBEF',
+ 'input-line': '#383D511F',
+ 'input-border-hover': '#CBCDD8',
+ 'surface-elevated': '#ffffff',
+ 'table-bg': '#ffffff',
+ // --- semantic / button (Figma collection) ---
+ 'button-hover-primary': 'rgba(255, 255, 255, 0.16)',
+ 'button-hover-secondary': 'rgba(0, 0, 0, 0.03)',
+ 'button-hover-tertiary': 'rgba(0, 0, 0, 0.04)',
+} as const;
+
+export const figmaDark = {
+ 'bg-max': '#0a0a0b',
+ 'bg-1': '#100f0f',
+ 'bg-2': '#1a1919',
+ 'bg-3': '#1f1e1e',
+ 'bg-4': '#2a2828',
+ 'bg-5': '#393737',
+ 'bg-6': '#494646',
+ 'border-0': 'rgba(255, 255, 255, 0.06)',
+ 'border-1': 'rgba(255, 255, 255, 0.08)',
+ 'border-2': 'rgba(255, 255, 255, 0.12)',
+ 'border-opaque': '#262626',
+ 'fg-max': '#ffffff',
+ 'fg-1': '#ffffff',
+ 'fg-2': '#bcbbbb',
+ 'fg-3': '#8f8e8e',
+ 'fg-4': '#636161',
+ 'fg-5': '#ffffff',
+ // Muted icon grey (search, sortable-column chevrons, …). Deliberately mode-agnostic — the same
+ // value in both maps — unlike the fg-* ramp steps.
+ 'fg-icon': '#A8A8A8',
+ selected: 'rgba(255, 255, 255, 0.06)',
+ 'blue-1': '#1a88f8',
+ 'blue-2': '#48abff',
+ 'blue-3': '#a9e7ff',
+ 'yellow-1': '#ffc42c',
+ 'yellow-2': '#ffd631',
+ 'yellow-3': '#fff7ae',
+ 'red-1': '#f24900',
+ 'red-2': '#ff8947',
+ 'red-3': '#ffc693',
+ 'purple-1': '#9391f7',
+ 'purple-2': '#bcbbff',
+ 'purple-3': '#e2e0ff',
+ 'green-1': '#1f807b',
+ 'green-2': '#63bbb6',
+ 'green-3': '#9debe7',
+ 'cyan-1': '#6bcef5',
+ 'cyan-2': '#b5e7fa',
+ 'cyan-3': '#dff6ff',
+ 'navy-1': '#1c4886',
+ 'navy-2': '#6188c0',
+ 'navy-3': '#b0d3ff',
+ 'data-red': '#E05269',
+ 'data-coral': '#FF7045',
+ 'data-orange': '#E68662',
+ 'data-honey': '#F59942',
+ 'data-yellow': '#FDC75A',
+ 'data-pear': '#FFE042',
+ 'data-light-green': '#C1E38D',
+ 'data-green': '#66C399',
+ 'data-matcha': '#92D492',
+ 'data-seafoam': '#78D3B3',
+ 'data-teal': '#8DE3CC',
+ 'data-lagoon': '#83DDDF',
+ 'data-blue': '#88D5ED',
+ 'data-azure': '#88C0FE',
+ 'data-purple': '#A5A3FF',
+ 'data-lavender': '#C9A1EF',
+ 'data-pink': '#E1A4D9',
+ 'shadow-low': 'rgba(0, 0, 0, 0.15)',
+ 'shadow-medium': 'rgba(0, 0, 0, 0.3)',
+ 'shadow-high': 'rgba(0, 0, 0, 0.35)',
+ 'shadow-strong': 'rgba(0, 0, 0, 0.5)',
+ 'shadow-stroke-1': 'rgba(255, 255, 255, 0.08)',
+ 'shadow-stroke-2': 'rgba(255, 255, 255, 0.1)',
+ ethereum: '#434b55',
+ focus: 'rgba(85, 167, 251, 0.3)',
+ scrim: 'rgba(71, 67, 67, 0.8)',
+ 'button-hover': 'rgba(255, 255, 255, 0.025)',
+ 'overlay-hover': 'rgba(255, 255, 255, 0.08)',
+ 'overlay-hover-subtle': 'rgba(255, 255, 255, 0.04)',
+ 'table-item-hover-1': '#1e1d1d',
+ 'table-item-hover-2': '#282727',
+ 'data-green-gho': '#5dff93',
+ // Gold for the favourited market star (mode-agnostic; Figma color(display-p3 1 0.7 0)).
+ 'favourite-star': '#FFB300',
+ // Alert "danger" severity red (icon + gradient); distinct from the muted error-* palette.
+ danger: '#DC2626',
+ // sGHO markets-banner gradient: a data-green wash at 6% fading to the banner's own surface.
+ 'sgho-banner-green': 'rgba(102, 195, 153, 0.06)',
+ 'wallet-modal-more-networks-label': 'rgba(255, 255, 255, 0.4)',
+ 'chain-testnet': '#bfc6d1',
+ 'chain-ethereum': '#7e8287',
+ 'chain-polygon': '#8347e5',
+ 'chain-base': '#0052ff',
+ 'chain-optimism': '#e84142',
+ 'chain-lens': '#36a136',
+ 'chain-arbitrum': '#28a0f0',
+ 'chain-blast': '#ffc700',
+ 'chain-scroll': '#f8cf6e',
+ 'chain-worldchain': '#ff9d00',
+ 'chain-zksync': '#8c8dfe',
+ bone: '#f6f7f4',
+ 'bg-4-hover': '#28282a',
+ // --- semantic tokens promoted from theme-file literals (SoT) ---
+ 'secondary-main': '#F48FB1',
+ 'secondary-light': '#F6A5C0',
+ 'secondary-dark': '#AA647B',
+ 'error-light': '#E57373',
+ 'error-dark': '#D32F2F',
+ 'error-text': '#FBB4AF',
+ 'error-bg': '#2E0C0A',
+ 'warning-light': '#FFB74D',
+ 'warning-dark': '#F57C00',
+ 'warning-text': '#FFDCA8',
+ 'warning-bg': '#301E04',
+ 'info-light': '#4FC3F7',
+ 'info-dark': '#0288D1',
+ 'info-text': '#A9E2FB',
+ 'info-bg': '#071F2E',
+ 'success-light': '#90FF95',
+ 'success-dark': '#388E3C',
+ 'success-text': '#C2E4C3',
+ 'success-bg': '#0A130B',
+ 'disabled-fg': '#EBEBEF4D',
+ 'disabled-bg': '#EBEBEF1F',
+ 'input-line': '#EBEBEF6B',
+ 'input-border-hover': '#CBCDD8',
+ 'surface-elevated': '#1E1E20',
+ 'table-bg': '#1A1919',
+ // --- semantic / button (Figma collection) ---
+ 'button-hover-primary': 'rgba(0, 0, 0, 0.16)',
+ 'button-hover-secondary': 'rgba(255, 255, 255, 0.04)',
+ 'button-hover-tertiary': 'rgba(255, 255, 255, 0.06)',
+} as const;
+
+// Token names shared by both modes (light is the common subset; dark adds a few extras).
+export type FigmaColorName = keyof typeof figmaLight;
+
+/** Resolve a single Figma color token for the active mode. */
+export const figmaColor = (mode: 'light' | 'dark', name: FigmaColorName) =>
+ mode === 'dark' ? figmaDark[name] : figmaLight[name];
+
+/**
+ * Pick the whole token map for a mode — the terse way to build the palette:
+ * const t = pickFigma(mode);
+ * text: { primary: t['fg-1'], secondary: t['fg-2'] }
+ */
+export const pickFigma = (mode: 'light' | 'dark'): Record =>
+ mode === 'dark' ? figmaDark : figmaLight;
+
+/**
+ * Terse, P3-safe accessor for the design tokens as CSS variables.
+ *
+ * The tokens are flattened onto the MUI palette root (see `theme.tsx`), so MUI generates a
+ * `--mui-palette-` custom property per token and the Display-P3 layer overrides those on
+ * wide-gamut displays. `figVars['bg-1']` therefore emits `var(--mui-palette-bg-1)`, which gets
+ * P3 + the structural sRGB fallback — unlike a raw `theme.palette['bg-1']` hex read, which does
+ * not. Use it in `styled()`, plain JS, and interpolated strings; inside `sx` the bare string
+ * form (`sx={{ bgcolor: 'bg-1' }}`) already resolves to the same var with no import.
+ *
+ * Gotcha: never pass a var-based color (this, a bare `sx` token, or `theme.vars.palette.*`) to a
+ * raw SVG/icon presentation attribute (``) — `var()` doesn't
+ * resolve there. Use a concrete hex, or apply the color via `sx`/`style` (CSS) instead.
+ *
+ * The `--mui-palette-` naming is coupled to MUI's var generation and to the tokens living
+ * at the palette root — the same coupling `collectP3Vars` (theme.tsx) relies on.
+ */
+export const figVars = Object.fromEntries(
+ Object.keys(figmaLight).map((name) => [name, `var(--mui-palette-${name})`])
+) as Record;
+
+/**
+ * Always-white, mode-independent. For text/icons that sit on a fixed colored surface (brand
+ * gradients, always-dark chips). A concrete hex — NOT a CSS var — so it also resolves in raw
+ * SVG/icon presentation attributes (`color=`/`fill=`), where `var()` does not.
+ */
+export const onAccent = '#ffffff';
+
+/**
+ * The shared "surface" box-shadow: a soft drop shadow plus a 1px ring that stands in
+ * for a border. Used by the secondary buttons, menus/paper, and the dashboard cards.
+ * `stroke` selects the ring token (cards use `shadow-stroke-1` for a slightly stronger hairline).
+ */
+export const figSurfaceShadow = (stroke: FigmaColorName = 'shadow-stroke-2'): string =>
+ `0px 2px 4px 0px ${figVars['shadow-low']}, 0px 0px 0px 1px ${figVars[stroke]}`;
diff --git a/src/utils/insetHighlight.ts b/src/utils/insetHighlight.ts
new file mode 100644
index 0000000000..e99eebc062
--- /dev/null
+++ b/src/utils/insetHighlight.ts
@@ -0,0 +1,77 @@
+import { CSSObject, Theme } from '@mui/material/styles';
+
+import { motion } from './motion';
+
+interface InsetHighlightOpts {
+ /** Only `transitions` is read — accepts the app theme or a plain MUI `Theme`. */
+ theme: Pick;
+ /** Corner radius of the highlight pseudo-element. */
+ radius: string | number;
+ /** Even inset applied to every side; overridden per-side by the props below. */
+ inset?: string | number;
+ top?: string | number;
+ right?: string | number;
+ bottom?: string | number;
+ left?: string | number;
+ /** Resting scale the highlight grows in from on activation (default 0.96). */
+ restScale?: number;
+ /**
+ * When set, the highlight is "on" at rest — a persistent selected state: full scale and this
+ * fill, rather than transparent-until-hover. Leave undefined for hover-only rows so no
+ * `background-color` is emitted at rest.
+ */
+ restFill?: string;
+}
+
+/**
+ * The inset-pseudo highlight recipe shared by the dropdown menu items (`MuiMenuItem` in
+ * `theme.tsx`) and the market-switcher option rows (`MarketSwitcher.tsx`). Draws the
+ * hover/selected fill on a `::before` inset from the row's edges — so adjacent highlights keep a
+ * visual gap while the physical row is unchanged — sitting behind the row's content
+ * (`zIndex: -1` under `isolation: isolate`) and growing in from `restScale` → 1.
+ *
+ * Pair with {@link insetHighlightActive} under the consumer's own hover/focus/selected selectors
+ * to set the fill and final scale (the trigger selectors differ per consumer: MUI classes for
+ * MenuItem, `:hover` + a JS boolean for the switcher).
+ */
+export const insetHighlightBase = ({
+ theme,
+ radius,
+ inset,
+ top,
+ right,
+ bottom,
+ left,
+ restScale = 0.96,
+ restFill,
+}: InsetHighlightOpts): CSSObject => ({
+ position: 'relative',
+ isolation: 'isolate',
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: top ?? inset ?? 0,
+ right: right ?? inset ?? 0,
+ bottom: bottom ?? inset ?? 0,
+ left: left ?? inset ?? 0,
+ zIndex: -1,
+ borderRadius: radius,
+ transform: restFill ? 'scale(1)' : `scale(${restScale})`,
+ transition: theme.transitions.create(['transform', 'background-color'], {
+ duration: motion.duration.hover,
+ }),
+ // Only emit a resting fill when persistently "on" — hover-only consumers (and the MenuItem
+ // refactor) stay identical, with no `background-color` until their own trigger fires.
+ ...(restFill ? { backgroundColor: restFill } : {}),
+ },
+});
+
+/**
+ * The "on" state for an {@link insetHighlightBase} highlight: the fill plus the grown-in scale.
+ * Apply under the consumer's hover / keyboard-focus / selected selectors, e.g.
+ * `'&:hover::before': insetHighlightActive(figVars['button-hover'])`.
+ */
+export const insetHighlightActive = (fill: string): CSSObject => ({
+ backgroundColor: fill,
+ transform: 'scale(1)',
+});
diff --git a/src/utils/motion.ts b/src/utils/motion.ts
new file mode 100644
index 0000000000..b1c60b0ea9
--- /dev/null
+++ b/src/utils/motion.ts
@@ -0,0 +1,28 @@
+/**
+ * Central motion tokens — the single source of truth for overlay/dialog animation
+ * timing across the app. Consumed by the theme's transition defaults and by the
+ * shared transition components (e.g. `ScaleFade`). Values mirror the reference
+ * project's overlay "feel": a fast, subtle pop.
+ *
+ * Kept in its own module (rather than in `theme.tsx`) so shared transitions can read
+ * these tokens without importing `theme.tsx`, which would create an import cycle
+ * (`theme` → `ScaleFade` → `theme`).
+ */
+export const motion = {
+ duration: {
+ /** dropdowns, menus, selects, popovers */
+ overlay: 100,
+ /** interactive control feedback — button hover/focus state transitions */
+ hover: 100,
+ /** deliberate hover fades on large targets */
+ hoverSlow: 150,
+ /** modal enter/exit — reserved for Phase 2 (modals are not animated yet) */
+ modal: 200,
+ /** mobile modal slide-up — reserved for Phase 2/3 */
+ modalMobile: 300,
+ },
+ easing: {
+ standard: 'ease',
+ smooth: 'cubic-bezier(0.19, 1, 0.22, 1)',
+ },
+} as const;
diff --git a/src/utils/theme.tsx b/src/utils/theme.tsx
index adee35697d..4a2dabfd29 100644
--- a/src/utils/theme.tsx
+++ b/src/utils/theme.tsx
@@ -1,23 +1,190 @@
-import {
- CheckCircleIcon,
- ChevronDownIcon,
- ExclamationCircleIcon,
- ExclamationIcon,
- InformationCircleIcon,
-} from '@heroicons/react/outline';
-import { SvgIcon, Theme, ThemeOptions } from '@mui/material';
-import { createTheme } from '@mui/material/styles';
+import { Box, SvgIcon, ThemeOptions } from '@mui/material';
+import { type CSSObject, createTheme, experimental_extendTheme } from '@mui/material/styles';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { ColorPartial } from '@mui/material/styles/createPalette';
+// Augments MUI's base `Theme` (the one component `sx`/`styled` callbacks receive) with `.vars`,
+// so `theme.vars.palette.*` typechecks app-wide, not only against this file's `AppTheme` param.
+import type {} from '@mui/material/themeCssVarsAugmentation';
import React from 'react';
+import {
+ AlertErrorIcon,
+ AlertInfoIcon,
+ AlertSuccessIcon,
+ AlertWarningIcon,
+} from 'src/components/icons/AlertIcons';
+import { ChevronUpDownIcon } from 'src/components/icons/ChevronUpDownIcon';
+import { ScaleFade } from 'src/components/primitives/transitions/ScaleFade';
+
+import { colorToP3 } from './colorToP3';
+import { type FigmaColorName, figSurfaceShadow, figVars, onAccent, pickFigma } from './figmaColors';
+import { insetHighlightActive, insetHighlightBase } from './insetHighlight';
+import { motion } from './motion';
+
+// The app theme is built with MUI's CSS-variables engine (`experimental_extendTheme`), so it
+// carries `.vars` (CSS custom-property refs like `figVars['bg-1']`) and
+// `.applyStyles(scheme, …)` for per-color-scheme overrides.
+type AppTheme = ReturnType;
+
+// MUI's `theme.applyStyles('dark', …)` needs the provider theme's `getColorSchemeSelector`,
+// which the raw `extendTheme` result (used to build the component overrides statically)
+// doesn't carry — so calling it there hits the classic `palette.mode` branch and throws (the
+// raw theme has no top-level `palette`). This helper inlines the exact CSS-vars selector
+// `applyStyles` emits, matching any ancestor with `data-mui-color-scheme="dark"` — the
+// element (app-wide) or a local wrapper (the dev showcase) — so both switch correctly.
+export const darkScheme = (styles: CSSObject): CSSObject => ({
+ '*:where([data-mui-color-scheme="dark"]) &': styles,
+});
+
+// Dropdown geometry: the menu paper's corner radius and the list's inset. The option-row
+// highlight radius is derived from these (paper radius − inset) to stay concentric, so keep
+// them together here — otherwise that relationship silently drifts.
+const MENU_PAPER_RADIUS = '0.75rem';
+const MENU_LIST_INSET = '0.38rem';
+
+/**
+ * The `::before` box the hover and disabled overlays both paint on: inset to the element's edges,
+ * behind its content but above its own background (`zIndex: -1` under `isolation: isolate`).
+ */
+const insetLayer: CSSObject = {
+ content: "''",
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ borderRadius: 'inherit',
+ zIndex: -1,
+};
+
+/**
+ * Composites a translucent `semantic/button` hover token over the button's own fill. Assigning one
+ * to `backgroundColor` would replace the base fill rather than tint it.
+ */
+export const hoverOverlay = (fill: string): CSSObject => ({
+ position: 'relative',
+ isolation: 'isolate',
+ '&::before': {
+ ...insetLayer,
+ transition: `background-color ${motion.duration.hover}ms ${motion.easing.standard}`,
+ },
+ '&:hover::before, &.Mui-focusVisible::before, &[aria-expanded="true"]::before': {
+ backgroundColor: fill,
+ },
+});
+
+/**
+ * The shared resting fill for the opaque "white pill" surfaces — the pill button variants and the
+ * Select trigger — so the tokens live here once instead of being restated ~470 lines apart. bg-3 in
+ * both modes, so it needs no `darkScheme` override.
+ */
+const surfaceFill = {
+ backgroundColor: figVars['bg-3'],
+ boxShadow: figSurfaceShadow(),
+};
+/** Opaque hover step for the Select trigger, which tints by fill rather than by overlay. */
+const surfaceFillHover = { backgroundColor: figVars['bg-4-hover'], boxShadow: figSurfaceShadow() };
+
+/**
+ * The "white pill" buttons, per the Figma `semantic/button` scale. Both sit on `surfaceFill` with a
+ * hairline ring instead of a border; they differ only in dark-mode fill and hover strength, so one
+ * factory keeps them from drifting. On hover the ring is re-asserted — the global `disableElevation`
+ * default otherwise strips it — and `border` is forced to none to suppress MUI's default outlined
+ * hover border.
+ */
+const pillStyle = (hoverToken: FigmaColorName, darkFill?: FigmaColorName) => ({
+ ...surfaceFill,
+ ...(darkFill ? darkScheme({ backgroundColor: figVars[darkFill] }) : {}),
+ ...hoverOverlay(figVars[hoverToken]),
+ color: figVars['fg-1'],
+ border: 'none',
+ '& .MuiButton-startIcon': {
+ color: figVars['fg-3'],
+ },
+ '&:hover, &.Mui-focusVisible, &[aria-expanded="true"]': {
+ boxShadow: figSurfaceShadow(),
+ border: 'none',
+ },
+});
+
+/** Secondary: bg-3 in both modes. */
+const secondaryPillStyle = pillStyle('button-hover-secondary');
+/** Tertiary: one step up the dark ramp, with a stronger hover tint. */
+const tertiaryPillStyle = pillStyle('button-hover-tertiary', 'bg-4');
+
+/** Shared disabled state for both pill variants. */
+const pillDisabled = {
+ color: figVars['fg-3'],
+ border: 'none',
+ boxShadow: figSurfaceShadow(),
+};
+
+// Alert severity surface: a gradient from the severity colour (left) fading to bg-2 (right), plus
+// the full colour + a 20% tint behind/inside the icon box. The two modes differ only in `tint` —
+// dark lifts it so the wash stays visible against the darker canvas — so the gradient itself is
+// written once here rather than duplicated into the dark override.
+const severityGradient = (color: string, tint: string) =>
+ `linear-gradient(90deg, color-mix(in srgb, ${color} ${tint}, transparent) 0%, ${figVars['bg-2']} 100%), ${figVars['bg-2']}`;
+
+const alertSeverityStyle = (color: string): CSSObject => ({
+ background: severityGradient(color, '3%'),
+ '.MuiAlert-icon': {
+ color,
+ backgroundColor: `color-mix(in srgb, ${color} 20%, transparent)`,
+ },
+ ...darkScheme({ background: severityGradient(color, '5%') }),
+});
+
+// Shared box geometry for the custom selection-control icons (checkbox + radio).
+const checkboxIconBox = { width: 18, height: 18, borderRadius: '0.375rem' };
+
+// Keyboard-focus ring shared by the buttons and the selection controls / switch: a 2px ring in the
+// element's own colour, offset 3px out.
+const focusRing = { outline: '2px solid currentColor', outlineOffset: '3px' } as const;
+
+// Selection-control (checkbox + radio) icon recipes — shared so the two never drift. The unchecked
+// box is transparent (it picks up whatever surface it sits on) with an inset border-0 hairline that
+// darkens to fg-4 on hover (keyed to the shared .MuiButtonBase-root both controls carry, so one
+// selector covers both); the checked box is a purple-1 fill centered on its glyph. Radio spreads
+// these and overrides borderRadius to a circle.
+const selectionControlResting = {
+ ...checkboxIconBox,
+ backgroundColor: 'transparent',
+ boxShadow: `inset 0 0 0 1px ${figVars['border-0']}`,
+ boxSizing: 'border-box' as const,
+ '.MuiButtonBase-root:hover &': {
+ boxShadow: `inset 0 0 0 1px ${figVars['fg-4']}`,
+ },
+ // Keyboard-focus ring (see `focusRing`), hugging the icon box. The focus class lands on the
+ // shared ButtonBase root, so key it off that.
+ '.MuiButtonBase-root.Mui-focusVisible &': focusRing,
+};
+const selectionControlChecked = {
+ ...checkboxIconBox,
+ backgroundColor: figVars['purple-1'],
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ // Keyboard-focus ring (see `focusRing`).
+ '.MuiButtonBase-root.Mui-focusVisible &': focusRing,
+};
+const selectionControlRootReset = {
+ root: {
+ '&:hover, &.Mui-focusVisible': {
+ backgroundColor: 'transparent',
+ },
+ },
+};
+
+// Soft shadow under the Switch's thumb.
+const controlThumbShadow = '0px 1px 1px rgba(0, 0, 0, 0.12)';
const theme = createTheme();
const {
typography: { pxToRem },
} = theme;
-const FONT = 'Inter, Arial';
+export const FONT = "'Inter Variable', Inter, Arial";
declare module '@mui/material/styles/createPalette' {
interface PaletteColor extends ColorPartial {}
@@ -30,30 +197,18 @@ declare module '@mui/material/styles/createPalette' {
default: string;
paper: string;
surface: string;
- surface2: string;
- header: string;
- disabled: string;
}
- interface Palette {
- gradients: {
- aaveGradient: string;
- newGradient: string;
- };
- other: {
- standardInputLine: string;
- };
- }
+ // Design tokens are flattened onto the palette root (see `getDesignTokens`), so each token is
+ // a first-class palette member. This also turns a token name that collides with a built-in
+ // palette key (e.g. `error`, `background`) into a compile error rather than a silent overwrite.
+ interface Palette extends Record {}
- interface PaletteOptions {
- gradients: {
- aaveGradient: string;
- newGradient: string;
- };
- }
+ interface PaletteOptions extends Partial> {}
}
interface TypographyCustomVariants {
+ base: React.CSSProperties;
display1: React.CSSProperties;
subheader1: React.CSSProperties;
subheader2: React.CSSProperties;
@@ -62,22 +217,20 @@ interface TypographyCustomVariants {
buttonM: React.CSSProperties;
buttonS: React.CSSProperties;
helperText: React.CSSProperties;
- tooltip: React.CSSProperties;
- main21: React.CSSProperties;
secondary21: React.CSSProperties;
- main16: React.CSSProperties;
secondary16: React.CSSProperties;
- main14: React.CSSProperties;
- secondary14: React.CSSProperties;
main12: React.CSSProperties;
- secondary12: React.CSSProperties;
+ statValue: React.CSSProperties;
+ statValueNoData: React.CSSProperties;
+ pageTitle: React.CSSProperties;
}
declare module '@mui/material/styles' {
interface TypographyVariants extends TypographyCustomVariants {}
- // allow configuration using `createTheme`
- interface TypographyVariantsOptions extends TypographyCustomVariants {}
+ // allow configuration using `createTheme` — partial, since the variants are supplied across
+ // two passes (the base set in `getDesignTokens`, the responsive ones in `createAppTheme`).
+ interface TypographyVariantsOptions extends Partial {}
interface BreakpointOverrides {
xsm: true;
@@ -89,6 +242,7 @@ declare module '@mui/material/styles' {
// Update the Typography's variant prop options
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
+ base: true;
display1: true;
subheader1: true;
subheader2: true;
@@ -97,16 +251,13 @@ declare module '@mui/material/Typography' {
buttonM: true;
buttonS: true;
helperText: true;
- tooltip: true;
- main21: true;
secondary21: true;
- main16: true;
secondary16: true;
- main14: true;
- secondary14: true;
main12: true;
- secondary12: true;
- h5: false;
+ statValue: true;
+ statValueNoData: true;
+ pageTitle: true;
+ h5: true;
h6: false;
subtitle1: false;
subtitle2: false;
@@ -117,99 +268,97 @@ declare module '@mui/material/Typography' {
}
}
+// Add a `tertiary` button variant (the secondary pill minus its ring/shadow).
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
- surface: true;
- gradient: true;
+ tertiary: true;
+ }
+}
+
+declare module '@mui/material/Paper' {
+ interface PaperPropsVariantOverrides {
+ modal: true;
+ card: true;
+ table: true;
}
}
export const getDesignTokens = (mode: 'light' | 'dark') => {
- const getColor = (lightColor: string, darkColor: string) =>
- mode === 'dark' ? darkColor : lightColor;
+ const t = pickFigma(mode); // ← the one line of setup
return {
breakpoints: {
- keys: ['xs', 'xsm', 'sm', 'md', 'lg', 'xl', 'xxl'],
+ keys: ['xs', 'xsm', 'sm', 'md', 'mdlg', 'lg', 'xl', 'xxl'],
values: { xs: 0, xsm: 640, sm: 760, md: 960, mdlg: 1125, lg: 1280, xl: 1575, xxl: 1800 },
},
palette: {
mode,
+ // Design tokens flattened onto the palette root → MUI generates a `--mui-palette-`
+ // var per token, so `sx={{ bgcolor: 'bg-1' }}` and `figVars['bg-1']` both resolve to it.
+ ...t,
primary: {
- main: getColor('#383D51', '#EAEBEF'),
- light: getColor('#62677B', '#F1F1F3'),
- dark: getColor('#292E41', '#D2D4DC'),
- contrast: getColor('#FFFFFF', '#0F121D'),
+ main: t['fg-1'],
+ light: t['fg-2'],
+ dark: t['fg-max'],
+ contrastText: t['bg-1'],
},
secondary: {
- main: getColor('#FF607B', '#F48FB1'),
- light: getColor('#FF607B', '#F6A5C0'),
- dark: getColor('#B34356', '#AA647B'),
+ main: t['secondary-main'],
+ light: t['secondary-light'],
+ dark: t['secondary-dark'],
},
error: {
- main: getColor('#BC0000B8', '#F44336'),
- light: getColor('#D26666', '#E57373'),
- dark: getColor('#BC0000', '#D32F2F'),
- '100': getColor('#4F1919', '#FBB4AF'), // for alert text
- '200': getColor('#F9EBEB', '#2E0C0A'), // for alert background
+ main: t['red-1'],
+ light: t['error-light'],
+ dark: t['error-dark'],
+ '100': t['error-text'], // alert text
+ '200': t['error-bg'], // alert background
},
warning: {
- main: getColor('#F89F1A', '#FFA726'),
- light: getColor('#FFCE00', '#FFB74D'),
- dark: getColor('#C67F15', '#F57C00'),
- '100': getColor('#63400A', '#FFDCA8'), // for alert text
- '200': getColor('#FEF5E8', '#301E04'), // for alert background
+ main: t['yellow-1'],
+ light: t['warning-light'],
+ dark: t['warning-dark'],
+ '100': t['warning-text'],
+ '200': t['warning-bg'],
},
info: {
- main: getColor('#0062D2', '#29B6F6'),
- light: getColor('#0062D2', '#4FC3F7'),
- dark: getColor('#002754', '#0288D1'),
- '100': getColor('#002754', '#A9E2FB'), // for alert text
- '200': getColor('#E5EFFB', '#071F2E'), // for alert background
+ main: t['blue-1'],
+ light: t['info-light'],
+ dark: t['info-dark'],
+ '100': t['info-text'],
+ '200': t['info-bg'],
},
success: {
- main: getColor('#4CAF50', '#66BB6A'),
- light: getColor('#90FF95', '#90FF95'),
- dark: getColor('#318435', '#388E3C'),
- '100': getColor('#1C4B1E', '#C2E4C3'), // for alert text
- '200': getColor('#ECF8ED', '#0A130B'), // for alert background
+ main: t['data-green'],
+ light: t['success-light'],
+ dark: t['success-dark'],
+ '100': t['success-text'],
+ '200': t['success-bg'],
},
text: {
- primary: getColor('#303549', '#F1F1F3'),
- secondary: getColor('#62677B', '#A5A8B6'),
- disabled: getColor('#D2D4DC', '#62677B'),
- muted: getColor('#A5A8B6', '#8E92A3'),
- highlight: getColor('#383D51', '#C9B3F9'),
+ primary: t['fg-1'],
+ secondary: t['fg-2'],
+ disabled: t['fg-4'],
+ muted: t['fg-3'],
},
background: {
- default: getColor('#F1F1F3', '#1B2030'),
- paper: getColor('#FFFFFF', '#292E41'),
- surface: getColor('#F7F7F9', '#383D51'),
- surface2: getColor('#F9F9FB', '#383D51'),
- header: getColor('#2B2D3C', '#1B2030'),
- disabled: getColor('#EAEBEF', '#EBEBEF14'),
- },
- divider: getColor('#EAEBEF', '#EBEBEF14'),
- action: {
- active: getColor('#8E92A3', '#EBEBEF8F'),
- hover: getColor('#F1F1F3', '#EBEBEF14'),
- selected: getColor('#EAEBEF', '#EBEBEF29'),
- disabled: getColor('#BBBECA', '#EBEBEF4D'),
- disabledBackground: getColor('#EAEBEF', '#EBEBEF1F'),
- focus: getColor('#F1F1F3', '#EBEBEF1F'),
+ default: t['bg-5'],
+ paper: t['surface-elevated'],
+ surface: t['bg-2'],
},
- other: {
- standardInputLine: getColor('#383D511F', '#EBEBEF6B'),
- },
- gradients: {
- aaveGradient: 'linear-gradient(248.86deg, #B6509E 10.51%, #2EBAC6 93.41%)',
- newGradient: 'linear-gradient(79.67deg, #8C3EBC 0%, #007782 95.82%)',
+ divider: t['border-0'],
+ action: {
+ active: t['fg-3'],
+ hover: t['button-hover'],
+ selected: t['selected'],
+ disabled: t['disabled-fg'],
+ disabledBackground: t['disabled-bg'],
+ focus: t['focus'],
},
},
spacing: 4,
typography: {
fontFamily: FONT,
- h5: undefined,
h6: undefined,
subtitle1: undefined,
subtitle2: undefined,
@@ -233,16 +382,14 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
},
h2: {
fontFamily: FONT,
- fontWeight: 600,
- letterSpacing: 'unset',
- lineHeight: '133.4%',
- fontSize: pxToRem(21),
+ fontWeight: 500,
+ lineHeight: '120%',
+ fontSize: pxToRem(24),
},
h3: {
fontFamily: FONT,
- fontWeight: 600,
- letterSpacing: pxToRem(0.15),
- lineHeight: '160%',
+ fontWeight: 500,
+ lineHeight: '120%',
fontSize: pxToRem(18),
},
h4: {
@@ -252,6 +399,12 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
lineHeight: pxToRem(24),
fontSize: pxToRem(16),
},
+ h5: {
+ fontFamily: FONT,
+ fontWeight: 500,
+ lineHeight: pxToRem(18),
+ fontSize: pxToRem(14),
+ },
subheader1: {
fontFamily: FONT,
fontWeight: 600,
@@ -266,6 +419,12 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
lineHeight: pxToRem(16),
fontSize: pxToRem(12),
},
+ base: {
+ fontFamily: FONT,
+ fontWeight: 400,
+ lineHeight: '100%',
+ fontSize: pxToRem(14),
+ },
description: {
fontFamily: FONT,
fontWeight: 400,
@@ -290,7 +449,8 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
buttonM: {
fontFamily: FONT,
fontWeight: 500,
- lineHeight: pxToRem(24),
+ letterSpacing: '-0.00563rem',
+ lineHeight: '1.25rem',
fontSize: pxToRem(14),
},
buttonS: {
@@ -308,32 +468,12 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
lineHeight: pxToRem(12),
fontSize: pxToRem(10),
},
- tooltip: {
- fontFamily: FONT,
- fontWeight: 400,
- letterSpacing: pxToRem(0.15),
- lineHeight: pxToRem(16),
- fontSize: pxToRem(12),
- },
- main21: {
- fontFamily: FONT,
- fontWeight: 800,
- lineHeight: '133.4%',
- fontSize: pxToRem(21),
- },
secondary21: {
fontFamily: FONT,
fontWeight: 500,
lineHeight: '133.4%',
fontSize: pxToRem(21),
},
- main16: {
- fontFamily: FONT,
- fontWeight: 600,
- letterSpacing: pxToRem(0.15),
- lineHeight: pxToRem(24),
- fontSize: pxToRem(16),
- },
secondary16: {
fontFamily: FONT,
fontWeight: 500,
@@ -341,20 +481,6 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
lineHeight: pxToRem(24),
fontSize: pxToRem(16),
},
- main14: {
- fontFamily: FONT,
- fontWeight: 600,
- letterSpacing: pxToRem(0.15),
- lineHeight: pxToRem(20),
- fontSize: pxToRem(14),
- },
- secondary14: {
- fontFamily: FONT,
- fontWeight: 500,
- letterSpacing: pxToRem(0.15),
- lineHeight: pxToRem(20),
- fontSize: pxToRem(14),
- },
main12: {
fontFamily: FONT,
fontWeight: 600,
@@ -362,18 +488,43 @@ export const getDesignTokens = (mode: 'light' | 'dark') => {
lineHeight: pxToRem(16),
fontSize: pxToRem(12),
},
- secondary12: {
- fontFamily: FONT,
- fontWeight: 500,
- letterSpacing: pxToRem(0.1),
- lineHeight: pxToRem(16),
- fontSize: pxToRem(12),
- },
},
} as ThemeOptions;
};
-export function getThemedComponents(theme: Theme) {
+/**
+ * Subtle press feedback shared by buttons and dropdown triggers: the control scales down
+ * slightly while active (pointer/touch down), and never when disabled. Pair with a `transform`
+ * transition (at `motion.duration.hover`) so the release animates back. Reduced-motion users
+ * get the scale instantly via the global `prefers-reduced-motion` rule in MuiCssBaseline.
+ */
+const pressScaleActive = {
+ '&:active:not(.Mui-disabled)': {
+ transform: 'scale(0.99)',
+ },
+};
+
+/**
+ * Disabled button treatment: the label/icon stay crisp while the button's own background (+ box
+ * shadow) render at 50% on an `opacity: 0.5` `::before` layer. Opacity is used (not color-mix /
+ * channel alpha) so the faded fill keeps its Display-P3 color; a box-shadow also has no opacity of
+ * its own, so fading a layer is the only clean way to halve it. `isolation: isolate` makes the root
+ * a stacking context so the `z-index: -1` layer sits behind the label, not behind the parent bg.
+ */
+const disabledFade = (opts: { color: string; before: CSSObject }): CSSObject => ({
+ color: opts.color,
+ backgroundColor: 'transparent',
+ border: 'none',
+ boxShadow: 'none',
+ isolation: 'isolate',
+ '&::before': {
+ ...insetLayer,
+ opacity: 0.5,
+ ...opts.before,
+ },
+});
+
+export function getThemedComponents(theme: AppTheme) {
return {
components: {
MuiSkeleton: {
@@ -386,27 +537,58 @@ export function getThemedComponents(theme: Theme) {
MuiOutlinedInput: {
styleOverrides: {
root: {
- borderRadius: '6px',
- borderColor: theme.palette.divider,
- '&:hover .MuiOutlinedInput-notchedOutline': {
- borderColor: '#CBCDD8',
+ borderRadius: '0.5rem',
+ // Text inputs (everything that isn't a Select): a bg-3 surface with the shared
+ // surface shadow (shadow-low drop + shadow-stroke-2 1px ring) instead of a border.
+ // Selects keep their own fill via the `:has(.MuiSelect-select)` block below.
+ '&:not(:has(.MuiSelect-select))': {
+ backgroundColor: figVars['bg-3'],
+ boxShadow: figSurfaceShadow(),
+ '& .MuiOutlinedInput-notchedOutline': { border: 'none' },
},
- '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
- borderColor: '#CBCDD8',
+ // Select trigger = the outlined-button surface: the same `surfaceFill` recipe the
+ // pill uses, same 0.5rem radius (from `root`). The tokens are shared rather than
+ // restated so the two can't drift. The notched border is dropped — the ring IS the
+ // outline — so there's no blueish or animated border; hover & open step the fill while
+ // the ring stays put. `pillStyle` itself isn't spread here: its fg-1 color,
+ // start-icon selector and `[aria-expanded]` selector are all wrong for an input (the
+ // attribute lands on the inner `.MuiSelect-select`, hence the `:has()` below).
+ '&:has(.MuiSelect-select)': {
+ ...surfaceFill,
+ // Animate the hover/open fill+ring step (was instant — the root had no transition).
+ transition: theme.transitions.create(['background-color', 'box-shadow'], {
+ duration: motion.duration.hover,
+ }),
+ '& .MuiOutlinedInput-notchedOutline': { border: 'none' },
+ // Open fill is keyed to the Select's actual open state (`aria-expanded` on the
+ // select), NOT `.Mui-focused`: a Select keeps focus after its menu closes, so a
+ // focus-based fill would linger after closing and while other fields are focused.
+ '&:hover, &:has(.MuiSelect-select[aria-expanded="true"])': {
+ ...surfaceFillHover,
+ '& .MuiOutlinedInput-notchedOutline': { border: 'none' },
+ },
+ // Keyboard-focus ring only (browser deems focus visible → keyboard nav, not the
+ // focus MUI restores to the trigger on close). Matches the outlined-button ring.
+ '&:has(.MuiSelect-select:focus-visible)': {
+ outline: `2px solid ${figVars['fg-1']}`,
+ outlineOffset: '3px',
+ },
+ // Disabled dropdown: inert to hover / pointer / touch (no hover fill step, no
+ // pointer cursor). The faded look still comes from MUI's `.Mui-disabled` text.
+ '&.Mui-disabled': {
+ pointerEvents: 'none',
+ },
},
},
},
},
- MuiSlider: {
- styleOverrides: {
- root: {
- '& .MuiSlider-thumb': {
- color: theme.palette.mode === 'light' ? '#62677B' : '#C9B3F9',
- },
- '& .MuiSlider-track': {
- color: theme.palette.mode === 'light' ? '#383D51' : '#9C93B3',
- },
- },
+ MuiButtonBase: {
+ defaultProps: {
+ // No ripple / pressed "splash" on any control (menu items, buttons, icon
+ // buttons, toggles, checkboxes, tabs, …). Interaction is conveyed by hover,
+ // keyboard focus, and the press-scale — not MUI's ripple. Set on ButtonBase so
+ // it covers every ButtonBase-derived component in one place.
+ disableRipple: true,
},
},
MuiButton: {
@@ -415,55 +597,155 @@ export function getThemedComponents(theme: Theme) {
},
styleOverrides: {
root: {
- borderRadius: '4px',
+ // Size to content + padding, not MUI's default 64px floor (which let buttons in tight
+ // flex rows squish below their content). Row action buttons re-add an even floor
+ // locally (ListButtonsColumn); deliberate collapses keep their own minWidth: 0.
+ minWidth: 'unset',
+ // Never wrap the label to a second line — buttons size to their text and stay one line
+ // even in tight flex rows (e.g. the sGHO markets banner's action row).
+ whiteSpace: 'nowrap',
+ // Hover/focus state transition at 100ms (overrides MUI's 250ms default).
+ // `transform` is included so the active-press scale animates in and out.
+ transition: theme.transitions.create(
+ ['background-color', 'box-shadow', 'border-color', 'color', 'transform'],
+ { duration: motion.duration.hover }
+ ),
+ // Subtle press feedback — scale down while active (not when disabled).
+ ...pressScaleActive,
+ // Keyboard-focus ring in the variant's own text color; ButtonBase zeroes the
+ // native outline, so we set our own (2px, offset 3px out).
+ '&.Mui-focusVisible': {
+ outline: '2px solid currentColor',
+ outlineOffset: '3px',
+ },
},
sizeLarge: {
...theme.typography.buttonL,
- padding: '10px 24px',
+ height: '48px',
+ padding: '0 24px',
+ borderRadius: '0.625rem',
},
sizeMedium: {
...theme.typography.buttonM,
- padding: '6px 12px',
+ height: '36px',
+ // Text-side padding; a start/end icon's -4px slot margin (MUI default) tightens
+ // the icon side to ~10px automatically.
+ padding: '0 0.88rem',
+ borderRadius: '0.5rem',
},
sizeSmall: {
- ...theme.typography.buttonS,
- padding: '0 6px',
+ // v3: small buttons use buttonM (14px / 500 / no uppercase) + 0.62rem side padding —
+ // the same label style as sizeMedium, at a compact height. (Was the legacy buttonS:
+ // uppercase 10px / 6px padding, which the button rework never migrated.)
+ ...theme.typography.buttonM,
+ height: '28px',
+ padding: '0 0.62rem',
+ borderRadius: '0.375rem',
},
},
variants: [
+ // Secondary pill (`variant="outlined"`): bg-3 in both modes.
{
- props: { variant: 'surface' },
+ props: { color: 'primary', variant: 'outlined' },
style: {
- color: theme.palette.common.white,
- border: '1px solid',
- borderColor: '#EBEBED1F',
- backgroundColor: '#383D51',
- '&:hover, &.Mui-focusVisible': {
- backgroundColor: theme.palette.background.header,
- },
+ ...secondaryPillStyle,
+ '&.Mui-disabled': pillDisabled,
},
},
{
- props: { variant: 'gradient' },
+ props: { variant: 'contained', color: 'primary' },
style: {
- color: theme.palette.common.white,
- background: theme.palette.gradients.aaveGradient,
- transition: 'all 0.2s ease',
- '&:hover, &.Mui-focusVisible': {
- background: theme.palette.gradients.aaveGradient,
- opacity: '0.9',
+ backgroundColor: figVars['fg-1'],
+ // Same lift as the outlined pill, but ringed in the button's own fill (not
+ // shadow-stroke-2) — the opaque bg already reads as a boundary, so the ring just
+ // needs to disappear into it while the drop-shadow layer still adds the lift.
+ boxShadow: figSurfaceShadow('fg-1'),
+ ...hoverOverlay(figVars['button-hover-primary']),
+ // The root focus ring uses `currentColor`, which here is contrastText (bg-1) —
+ // nearly the same shade as the page background, so it's invisible. Re-point it at
+ // fg-1 (same ink the outlined variant's ring uses) so it reads against the page.
+ '&.Mui-focusVisible': {
+ outlineColor: figVars['fg-1'],
},
+ // Disabled: crisp label, fg-1 fill at 50% (no box-shadow on contained).
+ '&.Mui-disabled': disabledFade({
+ color: figVars['bg-1'],
+ before: { backgroundColor: figVars['fg-1'] },
+ }),
},
},
+ // Tertiary pill: the app's default button.
{
- props: { color: 'primary', variant: 'outlined' },
+ props: { variant: 'tertiary', color: 'primary' },
style: {
- background: theme.palette.background.surface,
- borderColor: theme.palette.divider,
+ ...tertiaryPillStyle,
+ '&.Mui-disabled': pillDisabled,
},
},
],
},
+ MuiIconButton: {
+ styleOverrides: {
+ root: {
+ transition: theme.transitions.create(['background-color', 'color', 'transform'], {
+ duration: motion.duration.hover,
+ }),
+ // Subtle press feedback — scale down while active (not when disabled).
+ ...pressScaleActive,
+ // Keep the hover fill while the menu this button opens is expanded (open === hover).
+ // MUI's IconButton hover is `action.hover` (= button-hover), so match it.
+ '&[aria-expanded="true"]': {
+ backgroundColor: figVars['button-hover'],
+ },
+ },
+ },
+ },
+ MuiToggleButton: {
+ styleOverrides: {
+ root: {
+ transition: theme.transitions.create(
+ ['background-color', 'color', 'transform', 'opacity'],
+ {
+ duration: motion.duration.hover,
+ }
+ ),
+ // Subtle press feedback — scale down while active (not when disabled).
+ ...pressScaleActive,
+ },
+ },
+ },
+ MuiCheckbox: {
+ defaultProps: {
+ icon: ,
+ checkedIcon: (
+
+
+
+
+
+ ),
+ },
+ styleOverrides: selectionControlRootReset,
+ },
+ MuiRadio: {
+ defaultProps: {
+ // Circular twin of the custom checkbox — shares its recipe, overriding the shape.
+ icon: ,
+ checkedIcon: (
+
+
+
+ ),
+ },
+ styleOverrides: selectionControlRootReset,
+ },
MuiTypography: {
defaultProps: {
variant: 'description',
@@ -473,23 +755,22 @@ export function getThemedComponents(theme: Theme) {
h2: 'h2',
h3: 'h3',
h4: 'h4',
+ h5: 'p',
subheader1: 'p',
subheader2: 'p',
caption: 'p',
+ base: 'p',
description: 'p',
buttonL: 'p',
buttonM: 'p',
buttonS: 'p',
main12: 'p',
- main14: 'p',
- main16: 'p',
- main21: 'p',
- secondary12: 'p',
- secondary14: 'p',
secondary16: 'p',
secondary21: 'p',
+ statValue: 'p',
+ statValueNoData: 'p',
+ pageTitle: 'h1',
helperText: 'span',
- tooltip: 'span',
},
},
},
@@ -500,21 +781,56 @@ export function getThemedComponents(theme: Theme) {
},
MuiMenu: {
defaultProps: {
+ // Menu hard-defaults transitionDuration='auto' and forwards it explicitly,
+ // shadowing the MuiPopover default below — so menus/selects need the duration
+ // set here too. TransitionComponent is set explicitly as well (rather than
+ // relying on the inner Popover's own default) to keep the theme authoritative.
+ TransitionComponent: ScaleFade,
+ transitionDuration: motion.duration.overlay,
PaperProps: {
- elevation: 0,
variant: 'outlined',
style: {
minWidth: 240,
- marginTop: '4px',
},
},
},
+ styleOverrides: {
+ // Own the dropdown paper's look HERE (not only via PaperProps) so it survives
+ // components that inject their own paper slotProps and drop the theme's PaperProps —
+ // most notably Select, whose menu would otherwise lose the 8px offset + outlined
+ // surface and look nothing like our other dropdowns. `&&` outweighs the MuiPaper
+ // variant styles. With the 0.38rem list inset + 2rem rows (MuiMenuItem), every
+ // dropdown (Selects included) matches the settings menu.
+ paper: {
+ '&&': {
+ marginTop: '8px',
+ borderRadius: MENU_PAPER_RADIUS,
+ border: 'none',
+ boxShadow: figSurfaceShadow(),
+ backgroundColor: figVars['surface-elevated'],
+ },
+ // Dark surface at the SAME doubled specificity as the light fill above, so it wins
+ // in dark mode. (The darkScheme helper's single `&` lost to `&&`, which left the
+ // light paper — and light-looking options — showing in dark mode.)
+ '*:where([data-mui-color-scheme="dark"]) &&': {
+ backgroundColor: figVars['bg-2'],
+ },
+ '.MuiList-root': { padding: MENU_LIST_INSET },
+ },
+ },
+ },
+ MuiPopover: {
+ // Covers raw Popover usages (MarketSwitcher desktop, multiselects, swap inputs).
+ defaultProps: {
+ TransitionComponent: ScaleFade,
+ transitionDuration: motion.duration.overlay,
+ },
},
MuiList: {
styleOverrides: {
root: {
- '.MuiMenuItem-root+.MuiDivider-root, .MuiDivider-root': {
- marginTop: '4px',
+ '.MuiDivider-root': {
+ marginTop: '8px',
marginBottom: '4px',
},
},
@@ -527,21 +843,59 @@ export function getThemedComponents(theme: Theme) {
MuiMenuItem: {
styleOverrides: {
root: {
- padding: '12px 16px',
+ minHeight: '2rem',
+ // MUI relaxes MenuItem min-height to `auto` at ≥sm; re-assert 2rem there so
+ // every option row is a firm 2rem tall on desktop too.
+ [theme.breakpoints.up('sm')]: { minHeight: '2rem' },
+ padding: '0.31rem 0.38rem',
+ // The hover/selected highlight is a pseudo-element inset 1px top & bottom, so
+ // adjacent highlights keep a small gap while the row itself stays full-height — the
+ // hover target is continuous, so moving between rows never interrupts the highlight.
+ // Shared recipe (geometry + motion) lives in insetHighlight.ts; the radius is kept
+ // concentric with the menu paper (paper radius − list inset).
+ ...insetHighlightBase({
+ theme,
+ radius: `calc(${MENU_PAPER_RADIUS} - ${MENU_LIST_INSET})`,
+ top: '1px',
+ bottom: '1px',
+ }),
+ // Hover, keyboard focus (arrow-key nav sets .Mui-focusVisible), and the selected row
+ // all share one subtle highlight — the button-hover fill, never MUI's primary tint.
+ '&:hover::before, &.Mui-focusVisible::before, &.Mui-selected::before':
+ insetHighlightActive(figVars['button-hover']),
+ // Highlight lives on the pseudo above — keep the row's own background clear.
+ // The compound selected states are listed explicitly: MUI's base MenuItem paints
+ // `&.Mui-selected:hover` / `&.Mui-selected.Mui-focusVisible` with a primary tint at
+ // higher specificity than a lone `&.Mui-selected`, so without these the selected row
+ // would show a stronger fill than other rows on hover/keyboard-focus.
+ '&:hover, &.Mui-focusVisible, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible':
+ {
+ backgroundColor: 'transparent',
+ },
+ // A row's leading icon sits one step back from its label, exactly like a button's
+ // start-icon (fg-3 icon against fg-1 text — see `pillStyle`). Scoped to a
+ // DIRECT SvgIcon child so it only catches currentColor UI icons; brand artwork
+ // (TokenIcon, MarketLogo) is ``-based and unaffected.
+ '& > .MuiSvgIcon-root': {
+ color: figVars['fg-3'],
+ },
},
},
},
MuiListItemText: {
styleOverrides: {
root: {
- ...theme.typography.subheader1,
+ ...theme.typography.subheader2,
+ fontSize: pxToRem(14),
+ fontWeight: 400,
+ lineHeight: pxToRem(14),
},
},
},
MuiListItemIcon: {
styleOverrides: {
root: {
- color: theme.palette.primary.light,
+ color: theme.vars.palette.primary.light,
minWidth: 'unset !important',
marginRight: '12px',
},
@@ -558,26 +912,58 @@ export function getThemedComponents(theme: Theme) {
MuiPaper: {
styleOverrides: {
root: {
- borderRadius: '4px',
+ borderRadius: '8px',
},
},
variants: [
{
props: { variant: 'outlined' },
style: {
- border: `1px solid ${theme.palette.divider}`,
- boxShadow: '0px 0px 2px rgba(0, 0, 0, 0.2), 0px 2px 10px rgba(0, 0, 0, 0.1)',
- background:
- theme.palette.mode === 'light'
- ? theme.palette.background.paper
- : theme.palette.background.surface,
+ border: 'none',
+ boxShadow: figSurfaceShadow(),
+ background: figVars['surface-elevated'],
+ ...darkScheme({
+ background: figVars['bg-2'],
+ }),
},
},
{
props: { variant: 'elevation' },
style: {
boxShadow: '0px 2px 1px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.25)',
- ...(theme.palette.mode === 'dark' ? { backgroundImage: 'none' } : {}),
+ ...darkScheme({ backgroundImage: 'none' }),
+ },
+ },
+ {
+ props: { variant: 'modal' },
+ style: {
+ borderRadius: '0.75rem',
+ backgroundColor: figVars['bg-1'],
+ ...darkScheme({ backgroundColor: figVars['bg-2'] }),
+ boxShadow: `0 0 0 1px ${figVars['border-1']}, 0 4px 16px 0 ${figVars['shadow-medium']}`,
+ },
+ },
+ {
+ // Canonical content card surface — the module cards (reserve-overview, staking, sGho,
+ // …). surface-elevated in light / bg-2 in dark, 10px radius, the shared surface ring
+ // (shadow-stroke-1 hairline + soft drop). Asset tables use the `table` variant below.
+ props: { variant: 'card' },
+ style: {
+ backgroundColor: figVars['surface-elevated'],
+ ...darkScheme({ backgroundColor: figVars['bg-2'] }),
+ borderRadius: '10px',
+ boxShadow: figSurfaceShadow('shadow-stroke-1'),
+ },
+ },
+ {
+ // The `card` surface on the table fill — the single source of truth for ListWrapper and
+ // the standalone asset tables. Only differs from `card` in dark mode, so a table left on
+ // `card` by mistake is invisible in light and wrong in dark.
+ props: { variant: 'table' },
+ style: {
+ backgroundColor: figVars['table-bg'],
+ borderRadius: '10px',
+ boxShadow: figSurfaceShadow('shadow-stroke-1'),
},
},
],
@@ -589,10 +975,8 @@ export function getThemedComponents(theme: Theme) {
flexDirection: 'column',
flex: 1,
paddingBottom: '39px',
- [theme.breakpoints.up('xs')]: {
- paddingLeft: '8px',
- paddingRight: '8px',
- },
+ paddingLeft: '8px',
+ paddingRight: '8px',
[theme.breakpoints.up('xsm')]: {
paddingLeft: '20px',
paddingRight: '20px',
@@ -601,18 +985,29 @@ export function getThemedComponents(theme: Theme) {
paddingLeft: '48px',
paddingRight: '48px',
},
+ // 20px, not the 96px this used to carry. The box is still uncapped here, so padding IS
+ // the gutter: 96px made the content *narrower* at 960 (863px → 768px) than it was at
+ // 959, and left it 152px behind the page content all the way to 1279 — the header and
+ // footer visibly disagreed with the page they framed. This ladder must stay identical
+ // to whatever a page's own Container resolves to, or the two drift apart again.
[theme.breakpoints.up('md')]: {
- paddingLeft: '96px',
- paddingRight: '96px',
+ paddingLeft: '20px',
+ paddingRight: '20px',
},
[theme.breakpoints.up('lg')]: {
paddingLeft: '20px',
paddingRight: '20px',
+ maxWidth: '1280px',
},
+ // The `xl` gutter is only safe because `maxWidth` rises with it: 96px of padding inside
+ // a box capped at 1632px still yields 1440px of content (1632 − 2×96), so content grows
+ // 1383px → 1440px across 1575–1632 and then holds, meeting `xxl` exactly. Raising this
+ // padding *without* lifting the cap is the old bug — it takes width from the content
+ // instead of adding outer gutter. Never pad a capped box without widening the cap.
[theme.breakpoints.up('xl')]: {
- maxWidth: 'unset',
paddingLeft: '96px',
paddingRight: '96px',
+ maxWidth: '1632px',
},
[theme.breakpoints.up('xxl')]: {
paddingLeft: 0,
@@ -625,34 +1020,55 @@ export function getThemedComponents(theme: Theme) {
MuiSwitch: {
styleOverrides: {
root: {
- height: 20 + 6 * 2,
- width: 34 + 6 * 2,
- padding: 6,
+ width: '1.75rem',
+ height: '1.125rem',
+ padding: 0,
+ flexShrink: 0,
+ borderRadius: '9px',
+ // Keyboard-focus ring (see `focusRing`). The focus class lands on the inner
+ // switchBase, so key the root's ring off it.
+ '&:has(.Mui-focusVisible)': focusRing,
},
switchBase: {
- padding: 8,
+ padding: 0,
+ margin: '2px',
'&.Mui-checked': {
- transform: 'translateX(14px)',
+ transform: 'translateX(10px)',
'& + .MuiSwitch-track': {
- backgroundColor: theme.palette.success.main,
+ backgroundColor: figVars['purple-1'],
opacity: 1,
},
},
'&.Mui-disabled': {
- opacity: theme.palette.mode === 'dark' ? 0.3 : 0.7,
+ opacity: 0.7,
+ ...darkScheme({ opacity: 0.3 }),
},
},
thumb: {
- color: theme.palette.common.white,
- borderRadius: '6px',
- width: '16px',
- height: '16px',
- boxShadow: '0px 1px 1px rgba(0, 0, 0, 0.12)',
+ color: onAccent,
+ borderRadius: '50%',
+ width: '14px',
+ height: '14px',
+ boxShadow: controlThumbShadow,
},
track: {
opacity: 1,
- backgroundColor: theme.palette.action.active,
- borderRadius: '8px',
+ backgroundColor: figVars['bg-6'],
+ borderRadius: '9px',
+ },
+ },
+ },
+ MuiFormControlLabel: {
+ styleOverrides: {
+ root: {
+ // A Switch has no internal padding, so MUI's default -11px label offset (meant for
+ // padded checkboxes/radios) crams the switch against whatever precedes it in a row.
+ // Zero it for switch-labeled controls, and give the switch↔label text a 0.5rem gap.
+ // Checkbox/radio labels keep MUI's defaults.
+ '&:has(.MuiSwitch-root)': {
+ marginLeft: 0,
+ gap: '0.5rem',
+ },
},
},
},
@@ -661,7 +1077,7 @@ export function getThemedComponents(theme: Theme) {
{
props: { fontSize: 'large' },
style: {
- fontSize: pxToRem(32),
+ fontSize: pxToRem(40),
},
},
],
@@ -669,129 +1085,133 @@ export function getThemedComponents(theme: Theme) {
MuiTableCell: {
styleOverrides: {
root: {
- borderColor: theme.palette.divider,
+ borderColor: figVars['border-2'],
+ },
+ // Column labels are fg-3 app-wide. MUI defaults the `head` variant to text.primary
+ // (fg-1), which reads as body ink — this pins every cell to the muted
+ // header token, matching the `ListHeaderTitle` primitive the list-based tables use.
+ head: {
+ color: figVars['fg-3'],
},
},
},
MuiAlert: {
styleOverrides: {
root: {
- boxShadow: 'none',
- borderRadius: '4px',
- padding: '8px 12px',
- ...theme.typography.caption,
+ display: 'flex',
alignItems: 'flex-start',
- '.MuiAlert-message': {
- padding: 0,
- paddingTop: '2px',
- paddingBottom: '2px',
- },
+ gap: '0.88rem',
+ padding: '1rem 1.25rem',
+ borderRadius: '0.375rem',
+ boxShadow: figSurfaceShadow(),
+ // Icon box: a 2.5rem rounded square with a border-0 hairline. Its per-severity tint
+ // fill + icon color are set in the severity variants below.
'.MuiAlert-icon': {
- padding: 0,
+ margin: 0,
+ padding: '0.625rem',
+ width: '2.5rem',
+ height: '2.5rem',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '0.375rem',
+ boxShadow: `inset 0 0 0 1px ${figVars['border-0']}`,
opacity: 1,
'.MuiSvgIcon-root': {
- fontSize: pxToRem(20),
+ fontSize: '1rem',
+ flexShrink: 0,
},
},
- a: {
- ...theme.typography.caption,
+ // Message: Paragraph text in fg-max, centered against the icon box on a single line;
+ // multi-line grows and top-aligns via the container's flex-start.
+ '.MuiAlert-message': {
+ padding: 0,
+ alignSelf: 'center',
+ color: figVars['fg-max'],
+ fontFamily: FONT,
+ fontWeight: 400,
+ fontSize: pxToRem(14),
+ lineHeight: pxToRem(19),
+ },
+ // Title (AlertTitle): identical to the message text, one weight step up (500). No
+ // bespoke per-alert heading styling — overrides MUI's larger/heavier default + margins.
+ '.MuiAlertTitle-root': {
+ margin: 0,
+ marginBottom: '0.13rem',
+ color: figVars['fg-max'],
+ fontFamily: FONT,
fontWeight: 500,
+ fontSize: pxToRem(14),
+ lineHeight: pxToRem(19),
+ },
+ a: {
+ color: 'inherit',
+ fontWeight: 'inherit',
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
},
},
'.MuiButton-text': {
- ...theme.typography.caption,
- fontWeight: 500,
+ // Inline buttons (copy / switch-network / …) fully match the alert text — same font
+ // size/family/line-height, no uppercase — plus an underline. Otherwise they keep MUI's
+ // button typography and render a size off (most visibly in the small variant).
+ color: 'inherit',
+ // `font` shorthand inherits family/size/weight/line-height in one go; letter-spacing
+ // isn't part of it, so inherit that separately.
+ font: 'inherit',
+ letterSpacing: 'inherit',
+ textTransform: 'none',
textDecoration: 'underline',
padding: 0,
margin: 0,
minWidth: 'unset',
+ height: 'auto',
+ verticalAlign: 'baseline',
'&:hover': {
textDecoration: 'none',
background: 'transparent',
},
},
- },
- },
- defaultProps: {
- iconMapping: {
- error: (
-
-
-
- ),
- info: (
-
-
-
- ),
- success: (
-
-
-
- ),
- warning: (
-
-
-
- ),
- },
- },
- variants: [
- {
- props: { severity: 'error' },
- style: {
- color: theme.palette.error['100'],
- background: theme.palette.error['200'],
- a: {
- color: theme.palette.error['100'],
- },
- '.MuiButton-text': {
- color: theme.palette.error['100'],
+ // Compact sizing: tighter padding + a 2rem icon box (the glyph inside keeps the default
+ // 1rem size). Shared by both `small` and `small-icon`. `small` additionally shrinks the
+ // text to 0.75rem; `small-icon` keeps the default-size text (for dense inline chips,
+ // e.g. history status badges).
+ '&[data-size="small"], &[data-size="small-icon"]': {
+ padding: '0.75rem',
+ gap: '0.75rem',
+ '.MuiAlert-icon': {
+ width: '2rem',
+ height: '2rem',
+ padding: '0.53125rem 0.5rem 0.46875rem 0.5rem',
},
},
- },
- {
- props: { severity: 'info' },
- style: {
- color: theme.palette.info['100'],
- background: theme.palette.info['200'],
- a: {
- color: theme.palette.info['100'],
+ '&[data-size="small"]': {
+ '.MuiAlert-message': {
+ fontSize: '0.75rem',
+ lineHeight: '1.0125rem',
},
- '.MuiButton-text': {
- color: theme.palette.info['100'],
+ '.MuiAlertTitle-root': {
+ fontSize: '0.75rem',
+ lineHeight: '1.0125rem',
},
},
},
- {
- props: { severity: 'success' },
- style: {
- color: theme.palette.success['100'],
- background: theme.palette.success['200'],
- a: {
- color: theme.palette.success['100'],
- },
- '.MuiButton-text': {
- color: theme.palette.success['100'],
- },
- },
- },
- {
- props: { severity: 'warning' },
- style: {
- color: theme.palette.warning['100'],
- background: theme.palette.warning['200'],
- a: {
- color: theme.palette.warning['100'],
- },
- '.MuiButton-text': {
- color: theme.palette.warning['100'],
- },
- },
+ },
+ defaultProps: {
+ iconMapping: {
+ error: ,
+ info: ,
+ success: ,
+ warning: ,
},
+ },
+ variants: [
+ { props: { severity: 'error' }, style: alertSeverityStyle(figVars['danger']) },
+ { props: { severity: 'info' }, style: alertSeverityStyle(figVars['purple-1']) },
+ { props: { severity: 'success' }, style: alertSeverityStyle(figVars['data-green']) },
+ { props: { severity: 'warning' }, style: alertSeverityStyle(figVars['favourite-star']) },
],
},
MuiCssBaseline: {
@@ -801,48 +1221,140 @@ export function getThemedComponents(theme: Theme) {
fontWeight: 400,
fontSize: pxToRem(14),
minWidth: '375px',
+ backgroundColor: figVars['bg-1'],
'> div:first-of-type': {
- minHeight: '100vh',
+ minHeight: '100dvh',
display: 'flex',
flexDirection: 'column',
},
},
+ // Respect the OS "reduce motion" preference app-wide (incl. the dev showcase,
+ // since CssBaseline is injected once at the app root).
+ '@media (prefers-reduced-motion: reduce)': {
+ '*, *::before, *::after': {
+ animationDuration: '0.01ms !important',
+ animationIterationCount: '1 !important',
+ transitionDuration: '0.01ms !important',
+ scrollBehavior: 'auto !important',
+ },
+ },
},
},
MuiSvgIcon: {
styleOverrides: {
colorPrimary: {
- color: theme.palette.primary.light,
+ color: theme.vars.palette.primary.light,
},
},
},
MuiSelect: {
defaultProps: {
IconComponent: (props) => (
-
-
-
+
),
},
styleOverrides: {
outlined: {
- backgroundColor: theme.palette.background.surface,
+ // The trigger's fill + ring live on the OutlinedInput root (see MuiOutlinedInput)
+ // so they're rounded and wrapped like the outlined button; here just the text.
...theme.typography.buttonM,
- padding: '6px 12px',
- color: theme.palette.primary.light,
+ color: figVars['fg-1'],
},
},
},
MuiLinearProgress: {
styleOverrides: {
bar1Indeterminate: {
- background: theme.palette.gradients.aaveGradient,
+ background: figVars['purple-1'],
},
bar2Indeterminate: {
- background: theme.palette.gradients.aaveGradient,
+ background: figVars['purple-1'],
},
},
},
},
} as ThemeOptions;
}
+
+/**
+ * Assemble the full app MUI theme (CSS-variables mode): both color schemes' design tokens
+ * plus the component overrides. Single source of truth shared by the app root
+ * (`AppGlobalStyles`) and the dev component showcase, so they can't drift apart. Color
+ * scheme is switched via the `data-mui-color-scheme` attribute, not by rebuilding the theme.
+ */
+export const createAppTheme = () => {
+ const light = getDesignTokens('light');
+ const dark = getDesignTokens('dark');
+ const shared = {
+ breakpoints: light.breakpoints,
+ spacing: light.spacing,
+ typography: light.typography,
+ colorSchemes: {
+ light: { palette: light.palette },
+ dark: { palette: dark.palette },
+ },
+ };
+ // Build a base theme first so `getThemedComponents` can read its `.vars` (CSS-var refs),
+ // then rebuild with those overrides attached. (A build-once `theme.components = …` mutation
+ // trips MUI's `Components` typing, so the two-pass is the type-clean form.)
+ const base = experimental_extendTheme(shared);
+ const belowSm = base.breakpoints.down('sm');
+ const belowXsm = base.breakpoints.down('xsm');
+ const fromLg = base.breakpoints.up('lg');
+ return experimental_extendTheme({
+ ...shared,
+ typography: {
+ ...shared.typography,
+ statValue: { ...base.typography.h2, [belowSm]: base.typography.h4 },
+ statValueNoData: {
+ ...base.typography.secondary21,
+ [belowSm]: base.typography.secondary16,
+ },
+ pageTitle: {
+ ...base.typography.h1,
+ [belowXsm]: base.typography.h2,
+ [fromLg]: base.typography.display1,
+ },
+ },
+ components: getThemedComponents(base).components,
+ });
+};
+
+// --- Display-P3 override layer -------------------------------------------------------------
+
+const isColorValue = (v: string) => v.startsWith('#') || v.startsWith('rgb');
+
+// Walk a color scheme's palette and, for every solid color leaf, emit a P3 override keyed to
+// the CSS variable MUI generates for it (`--mui-palette-`). Non-color
+// leaves (numbers, `mode`, channel strings like "32 29 29", gradients) are skipped.
+const collectP3Vars = (
+ node: Record,
+ path: string[],
+ out: Record
+) => {
+ Object.entries(node).forEach(([key, value]) => {
+ if (typeof value === 'string' && isColorValue(value)) {
+ out[`--mui-palette-${[...path, key].join('-')}`] = colorToP3(value);
+ } else if (value && typeof value === 'object') {
+ collectP3Vars(value as Record, [...path, key], out);
+ }
+ });
+};
+
+/**
+ * Build Display-P3 overrides for the generated `--mui-palette-*` CSS variables — one entry
+ * per solid color token, per color scheme. Injected under `@supports (color-gamut: p3)` so
+ * wide-gamut displays get the richer color while everything else keeps the sRGB base var.
+ * (Alpha-composited tints via MUI's `rgba( / a)` stay sRGB — see migration notes.)
+ */
+export const buildP3Overrides = (theme: AppTheme) => {
+ const forScheme = (scheme?: { palette?: unknown }) => {
+ const out: Record = {};
+ collectP3Vars((scheme?.palette ?? {}) as Record, [], out);
+ return out;
+ };
+ return {
+ light: forScheme(theme.colorSchemes.light),
+ dark: forScheme(theme.colorSchemes.dark),
+ };
+};