From 70c665fbc686df6fd78c4f673144151c71ce9954 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 26 May 2025 17:04:58 +0200 Subject: [PATCH 01/13] feat: llamalend breakpoint improvements - make adjustments so the table fits in very small screens - adjustments to chips so they fit around the breakpoints - autosize columns - unify graph columns into a single one - disable inverted theme on hover - new metric unit (multiplier) --- .../LlamaMarketExpandedPanel.tsx | 38 ++++++++-------- .../loan/components/PageLlamaMarkets/Page.tsx | 4 +- .../PageLlamaMarkets/cells/LineGraphCell.tsx | 2 +- .../cells/MarketTitleCell/MarketBadges.tsx | 20 +++++---- .../cells/MarketTitleCell/MarketTitleCell.tsx | 14 +++++- .../PageLlamaMarkets/cells/RateCell.tsx | 8 ++-- .../PageLlamaMarkets/cells/cell.format.ts | 1 + .../components/PageLlamaMarkets/columns.tsx | 24 ++-------- .../hooks/useLlamaMarketsColumnVisibility.tsx | 8 +++- .../src/shared/ui/DataTable/DataCell.tsx | 7 ++- .../src/shared/ui/DataTable/DataRow.tsx | 8 +++- .../src/shared/ui/DataTable/DataTable.tsx | 1 - .../src/shared/ui/DataTable/HeaderCell.tsx | 1 - .../shared/ui/DataTable/data-table.utils.ts | 6 +-- .../src/shared/ui/InvertOnHover.tsx | 45 +++---------------- .../curve-ui-kit/src/shared/ui/Metric.tsx | 13 ++++-- .../src/themes/design/1_sizes_spaces.ts | 1 - .../cypress/e2e/loan/llamalend-markets.cy.ts | 1 + 18 files changed, 95 insertions(+), 107 deletions(-) diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx index 0ee0af476d..03d4bafaec 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx @@ -3,6 +3,7 @@ import { LineGraphCell } from '@/loan/components/PageLlamaMarkets/cells' import { LlamaMarketColumnId } from '@/loan/components/PageLlamaMarkets/columns.enum' import { FavoriteMarketButton } from '@/loan/components/PageLlamaMarkets/FavoriteMarketButton' import { useUserMarketStats } from '@/loan/entities/llama-market-stats' +import { ArrowRight } from '@carbon/icons-react' import Button from '@mui/material/Button' import Stack from '@mui/material/Stack' import { t } from '@ui-kit/lib/i18n' @@ -15,40 +16,38 @@ import type { LlamaMarket } from '../../entities/llama-markets' export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { original: market } }) => { const { data: earnings, error: earningsError } = useUserMarketStats(market, LlamaMarketColumnId.UserEarnings) const { data: deposited, error: depositedError } = useUserMarketStats(market, LlamaMarketColumnId.UserDeposited) + const { leverage, utilizationPercent, liquidityUsd, userHasPosition, url, address, rates } = market return ( <> + {t`Market Details`} - - + + + + } > - + - {market.rates.lend && ( - <> - - - - )} - + + {leverage > 0 && } - {market.userHasPosition && ( + {userHasPosition && ( {earnings?.earnings != null && } {deposited?.deposited != null && ( @@ -59,9 +58,10 @@ export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { or diff --git a/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx b/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx index 5b26ae94a9..a61f22feb4 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx @@ -14,6 +14,7 @@ import { import { useLlamaMarkets } from '@/loan/entities/llama-markets' import { invalidateAllUserMintMarkets, invalidateMintMarkets, setMintMarkets } from '@/loan/entities/mint-markets' import useStore from '@/loan/store/useStore' +import { useMediaQuery } from '@mui/material' import Box from '@mui/material/Box' import Skeleton from '@mui/material/Skeleton' import { useUserProfileStore } from '@ui-kit/features/user-profile' @@ -67,12 +68,13 @@ export const LlamaMarketsPage = (props: CrvUsdServerData) => { const { address } = useAccount() const { data, isError, isLoading } = useLlamaMarkets(address) const minLiquidity = useUserProfileStore((s) => s.hideSmallPools) ? SMALL_POOL_TVL : 0 + const isTiny = useMediaQuery('(max-width:400px)') const bannerHeight = useStore((state) => state.layout.height.globalAlert) const headerHeight = useHeaderHeight(bannerHeight) const showSkeleton = !data && (!isError || isLoading) // on initial render isLoading is still false return ( - + {showSkeleton ? ( ) : ( diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/LineGraphCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/LineGraphCell.tsx index 1ff7fcd23b..c63ab47f8c 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/LineGraphCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/LineGraphCell.tsx @@ -10,7 +10,7 @@ import { t } from '@ui-kit/lib/i18n' import { DesignSystem } from '@ui-kit/themes/design' import { RateType, useSnapshots } from '../hooks/useSnapshots' -const graphSize = { width: 172, height: 48 } +const graphSize = { width: 100, height: 48 } /** * Get the color for the line graph. Will be green if the last value is higher than the first, red if lower, and blue if equal. diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx index 741e6f75ed..e3abbcb993 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx @@ -1,11 +1,10 @@ import { getRewardsDescription } from '@/loan/components/PageLlamaMarkets/cells/MarketTitleCell/cell.utils' import { FavoriteMarketButton } from '@/loan/components/PageLlamaMarkets/FavoriteMarketButton' -import { useFavoriteMarket } from '@/loan/entities/favorite-markets' import { LlamaMarket, LlamaMarketType } from '@/loan/entities/llama-markets' import Box from '@mui/material/Box' import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' -import { useTheme } from '@mui/material/styles' +import Typography from '@mui/material/Typography' import useMediaQuery from '@mui/material/useMediaQuery' import { t } from '@ui-kit/lib/i18n' import { RewardIcons } from '@ui-kit/shared/ui/RewardIcon' @@ -26,9 +25,8 @@ const poolTypeTooltips: Record string> = { /** Displays badges for a pool, such as the chain icon and the pool type. */ export const MarketBadges = ({ market: { address, rewards, type, leverage } }: { market: LlamaMarket }) => { - const [isFavorite, toggleFavorite] = useFavoriteMarket(address) const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) - const iconColor = useTheme().design.Text.TextColors.Highlight + const isSmall = useMediaQuery('(max-width:1250px)') return ( @@ -42,11 +40,15 @@ export const MarketBadges = ({ market: { address, rewards, type, leverage } }: { {leverage > 0 && ( - + {isMobile ? ( + 🔥 + ) : ( + + )} )} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx index cdf232c663..b3486b6bc9 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx @@ -21,7 +21,12 @@ export const MarketTitleCell = ({ row: { original: market } }: CellContext - + t.breakpoints.down('tablet')) ? 'tableCellMBold' : 'tableCellL'} + direction="row" + gap={2} + > ) => e.preventDefault(), })} + sx={{ + // for very small screens, truncate the text and limit to a maximum width + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + maxWidth: '40vw', // adjust as needed + }} > {market.assets.collateral.symbol} - {market.assets.borrowed.symbol} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx index efc219208b..0754132b23 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx @@ -3,13 +3,14 @@ import { LlamaMarket } from '@/loan/entities/llama-markets' import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' +import useMediaQuery from '@mui/material/useMediaQuery' import useIntersectionObserver from '@ui-kit/hooks/useIntersectionObserver' import { t } from '@ui-kit/lib/i18n' import { RewardIcons } from '@ui-kit/shared/ui/RewardIcon' import { Tooltip } from '@ui-kit/shared/ui/Tooltip' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { RateType, useSnapshots } from '../hooks/useSnapshots' -import { formatPercent, getRewardsAction } from './cell.format' +import { formatPercent, formatPercentFixed, getRewardsAction } from './cell.format' import { RateTooltipContent } from './RateCellTooltip' const { Spacing } = SizesAndSpaces @@ -21,6 +22,7 @@ export const RateCell = ({ market, type }: { market: LlamaMarket; type: RateType const { rewards, type: marketType } = market const rewardsAction = getRewardsAction(marketType, type) const poolRewards = rewards.filter(({ action }) => action == rewardsAction) + const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) return ( - {averageRate != null && formatPercent(averageRate)} + {isMobile ? rate != null && formatPercentFixed(rate) : averageRate != null && formatPercentFixed(averageRate)} - {rate != null && ( + {!isMobile && rate != null && ( {formatPercent(rate)} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/cell.format.ts b/apps/main/src/loan/components/PageLlamaMarkets/cells/cell.format.ts index ae0ab98bf3..1ff8a875b2 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/cell.format.ts +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/cell.format.ts @@ -3,6 +3,7 @@ import { LlamaMarketType } from '@/loan/entities/llama-markets' import type { RewardsAction } from '@ui/CampaignRewards/types' export const formatPercent = (rate: number) => `${rate.toPrecision(4)}%` +export const formatPercentFixed = (rate: number) => `${rate.toFixed(2)}%` export const getRewardsAction = (marketType: LlamaMarketType, type: RateType): RewardsAction => marketType === LlamaMarketType.Mint ? 'loan' : type == 'borrow' ? 'borrow' : 'supply' diff --git a/apps/main/src/loan/components/PageLlamaMarkets/columns.tsx b/apps/main/src/loan/components/PageLlamaMarkets/columns.tsx index 684cdc9ea1..839bde07e0 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/columns.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/columns.tsx @@ -35,7 +35,6 @@ export const LLAMA_MARKET_COLUMNS = [ header: t`Health`, cell: PercentageCell, meta: { type: 'numeric', hideZero: true }, - size: ColumnWidth.sm, sortUndefined: 'last', }), columnHelper.display({ @@ -43,7 +42,6 @@ export const LLAMA_MARKET_COLUMNS = [ header: t`Borrow Amount`, cell: PriceCell, meta: { type: 'numeric', borderRight: true }, - size: ColumnWidth.sm, sortUndefined: 'last', }), columnHelper.display({ @@ -51,7 +49,6 @@ export const LLAMA_MARKET_COLUMNS = [ header: t`My Earnings`, cell: PriceCell, meta: { type: 'numeric' }, - size: ColumnWidth.sm, sortUndefined: 'last', }), columnHelper.display({ @@ -59,7 +56,6 @@ export const LLAMA_MARKET_COLUMNS = [ header: t`Supplied Amount`, cell: PriceCell, meta: { type: 'numeric', borderRight: true }, - size: ColumnWidth.sm, filterFn: boolFilterFn, sortUndefined: 'last', }), @@ -68,41 +64,29 @@ export const LLAMA_MARKET_COLUMNS = [ header: t`7D Avg Borrow Rate`, cell: (c) => , meta: { type: 'numeric' }, - size: ColumnWidth.sm, sortUndefined: 'last', }), - columnHelper.accessor('rates.borrow', { - id: LlamaMarketColumnId.BorrowChart, - header: t`7D Borrow Rate Chart`, - cell: (c) => , - size: ColumnWidth.md, - }), columnHelper.accessor('rates.lend', { id: LlamaMarketColumnId.LendRate, header: t`7D Avg Supply Yield`, cell: (c) => , meta: { type: 'numeric' }, - size: ColumnWidth.sm, sortUndefined: 'last', }), - columnHelper.accessor('rates.lend', { - id: LlamaMarketColumnId.LendChart, - header: t`7D Supply Yield Chart`, - cell: (c) => , - size: ColumnWidth.md, - sortUndefined: 'last', + columnHelper.accessor('rates.borrow', { + id: LlamaMarketColumnId.BorrowChart, + header: t`7D Rate Chart`, + cell: (c) => , }), columnHelper.accessor(LlamaMarketColumnId.UtilizationPercent, { header: t`Utilization`, cell: PercentageCell, meta: { type: 'numeric' }, - size: ColumnWidth.sm, }), columnHelper.accessor(LlamaMarketColumnId.LiquidityUsd, { header: t`Available Liquidity`, cell: CompactUsdCell, meta: { type: 'numeric' }, - size: ColumnWidth.sm, }), // Following columns are used in tanstack filter, but they are displayed together in MarketTitleCell hidden(LlamaMarketColumnId.Chain, LlamaMarketColumnId.Chain, multiFilterFn), diff --git a/apps/main/src/loan/components/PageLlamaMarkets/hooks/useLlamaMarketsColumnVisibility.tsx b/apps/main/src/loan/components/PageLlamaMarkets/hooks/useLlamaMarketsColumnVisibility.tsx index 187391b058..999dca8a37 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/hooks/useLlamaMarketsColumnVisibility.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/hooks/useLlamaMarketsColumnVisibility.tsx @@ -34,13 +34,18 @@ export const createLlamaMarketsColumnOptions = (hasPositions: boolean | undefine active: true, enabled: true, }, + { + label: t`Chart`, + columns: [LlamaMarketColumnId.BorrowChart], + active: true, + enabled: true, + }, ], }, { label: t`Borrow`, options: [ { columns: [LlamaMarketColumnId.BorrowRate], active: true, enabled: true }, - { label: t`Chart`, columns: [LlamaMarketColumnId.BorrowChart], active: true, enabled: true }, { label: t`Borrow Details`, columns: [LlamaMarketColumnId.UserHealth, LlamaMarketColumnId.UserBorrowed], @@ -53,7 +58,6 @@ export const createLlamaMarketsColumnOptions = (hasPositions: boolean | undefine label: t`Lend`, options: [ { columns: [LlamaMarketColumnId.LendRate], active: true, enabled: true }, - { label: t`Chart`, columns: [LlamaMarketColumnId.LendChart], active: false, enabled: true }, { label: t`Lend Details`, columns: [LlamaMarketColumnId.UserEarnings, LlamaMarketColumnId.UserDeposited], diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx index 65edb9335e..83083585b8 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx @@ -16,10 +16,13 @@ export const DataCell = ({ cell, isMobile, isLast, + isFirst, }: { cell: Cell isMobile: boolean - isLast: boolean // todo: get rid of column hidden meta, use column.getIsLastColumn() + // todo: get rid of column hidden meta and use visibility + use column.getIsLastColumn() + isFirst: boolean + isLast: boolean }) => { const { column, row } = cell const { variant, borderRight } = column.columnDef.meta ?? {} @@ -37,7 +40,7 @@ export const DataCell = ({ component="td" sx={{ ...(!showCollapseIcon && sx), - ...getExtraColumnPadding(column), + ...getExtraColumnPadding({ isFirst, isLast }), ...(borderRight && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), }} data-testid={`data-table-cell-${column.id}`} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index dfe6a7406d..aed1772aa8 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -64,7 +64,13 @@ export const DataRow = ({ onClick={isMobile ? () => row.toggleExpanded() : onClickDesktop} > {visibleCells.map((cell, index) => ( - + ))} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx index fc8ea3d001..f1554f2f7a 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx @@ -36,7 +36,6 @@ export const DataTable = ({ }) => ( t.design.Layer[1].Fill, borderCollapse: 'separate' /* Don't collapse to avoid funky stuff with the sticky header */, }} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx index a5b3a4baeb..6753814752 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx @@ -33,7 +33,6 @@ export const HeaderCell = ({ header }: { header: Header `1px solid ${t.design.Layer[1].Outline}` }), }} colSpan={header.colSpan} - width={header.getSize()} onClick={column.getToggleSortingHandler()} data-testid={`data-table-header-${column.id}`} variant="tableHeaderS" diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts b/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts index fb7c7b4448..26f2e2d8de 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts @@ -26,9 +26,9 @@ const { Spacing } = SizesAndSpaces * In the figma design, the first and last columns seem to be aligned to the table title. * However, the normal padding causes them to be misaligned. */ -export const getExtraColumnPadding = (column: Column) => ({ - ...(column.getIsFirstColumn() && { paddingInlineStart: Spacing.md }), - ...(column.getIsLastColumn() && { paddingInlineEnd: Spacing.md }), +export const getExtraColumnPadding = ({ isFirst, isLast }: { isFirst: boolean; isLast: boolean }) => ({ + ...(isFirst && { paddingInlineStart: Spacing.md }), + ...(isLast && { paddingInlineEnd: Spacing.md }), }) export type FilterProps = { diff --git a/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx b/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx index 02db86ae45..8d1fd865d8 100644 --- a/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx +++ b/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx @@ -1,11 +1,6 @@ -import { cloneElement, type ReactElement } from 'react' +import { type ReactElement } from 'react' import type { Theme } from '@mui/material' -import { useClassObserver } from '@ui-kit/hooks/useClassObserver' -import { useSwitch } from '@ui-kit/hooks/useSwitch' -import { InvertTheme } from '@ui-kit/shared/ui/ThemeProvider' -import { TransitionFunction } from '@ui-kit/themes/design/0_primitives' import type { SxProps } from '@ui-kit/utils' -import { classNames, CypressHoverClass, useNativeEventInCypress } from '@ui-kit/utils/dom' /** * A component that inverts the theme when hovered. @@ -37,36 +32,8 @@ type InvertOnHoverProps = { disabled?: boolean } -const defaultHoverColor = (t: Theme) => t.design.Layer.TypeAction.Hover - -export const InvertOnHover = ({ - children: child, - hoverEl, - hoverColor = defaultHoverColor, - disabled, -}: InvertOnHoverProps) => { - const [isHover, onMouseEnter, onMouseLeave] = useSwitch(false) - const inverted = useClassObserver(hoverEl, 'Mui-focusVisible') || isHover - const childSx = (child.props.sx as Record) ?? {} - - useNativeEventInCypress(hoverEl, 'mouseenter', onMouseEnter) - if (disabled) return child - - return ( - - {cloneElement(child, { - ...child.props, - className: classNames(inverted && CypressHoverClass, child.props?.className), - onMouseEnter, - onMouseLeave, - sx: { - ...childSx, - color: (theme) => theme.palette.text.secondary, // by default components have color: 'inherit' which breaks the inverted theme - transition: [`background-color ${TransitionFunction}`, childSx['transition']].filter(Boolean).join(', '), - '&:hover': { ...childSx['&:hover'], backgroundColor: hoverColor }, - ...(inverted && { backgroundColor: hoverColor }), - }, - })} - - ) -} +/** + * A component that inverts the theme when hovered. Currently disabled due to performance issues. + * In the future we want to use css variables to achieve the same effect without fully changing the theme. + */ +export const InvertOnHover = ({ children }: InvertOnHoverProps) => children diff --git a/packages/curve-ui-kit/src/shared/ui/Metric.tsx b/packages/curve-ui-kit/src/shared/ui/Metric.tsx index d207c9ab61..60e0686ae9 100644 --- a/packages/curve-ui-kit/src/shared/ui/Metric.tsx +++ b/packages/curve-ui-kit/src/shared/ui/Metric.tsx @@ -53,14 +53,21 @@ const percentage: UnitOptions = { abbreviate: false, } -export const UNITS = ['dollar', 'percentage'] as const -type Unit = (typeof UNITS)[number] | UnitOptions +const multiplier: UnitOptions = { + symbol: 'x', + position: 'suffix', + abbreviate: true, +} -const UNIT_MAP: Record<(typeof UNITS)[number], UnitOptions> = { +const UNIT_MAP = { dollar, percentage, + multiplier, } as const +type Unit = keyof typeof UNIT_MAP | UnitOptions +export const UNITS = Object.keys(UNIT_MAP) as unknown as keyof typeof UNIT_MAP + // Default value formatter. const formatValue = (value: number, decimals?: number): string => value.toLocaleString(undefined, { diff --git a/packages/curve-ui-kit/src/themes/design/1_sizes_spaces.ts b/packages/curve-ui-kit/src/themes/design/1_sizes_spaces.ts index f0f65c268c..02c9eabc9b 100644 --- a/packages/curve-ui-kit/src/themes/design/1_sizes_spaces.ts +++ b/packages/curve-ui-kit/src/themes/design/1_sizes_spaces.ts @@ -267,7 +267,6 @@ export const SizesAndSpaces = { column: MappedColumnWidth, }, MinWidth: { - table: { desktop: '60rem' }, // 960px tableHeader: '50rem', // 800px select: '5rem', // 80px }, diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index 99815454a0..36e361f9b1 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -124,6 +124,7 @@ describe(`LlamaLend Markets`, () => { .last() .scrollIntoView({ offset: { top: -height / 2, left: 0 } }) // scroll to the last row, make sure it's still visible if (breakpoint == 'mobile') { + cy.get(`[data-testid="expand-icon"]`).last().scrollIntoView() cy.get(`[data-testid="expand-icon"]`).last().click() } cy.wait('@lend-snapshots') From b5d91aeb487998f579df8fb9214f10ce02011f41 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 26 May 2025 20:48:47 +0200 Subject: [PATCH 02/13] feat: LlamaMarketExpandedPanel full width graph --- .../LlamaMarketExpandedPanel.tsx | 112 ++++++++++++------ .../PageLlamaMarkets/cells/LineGraphCell.tsx | 5 +- apps/main/src/loan/store/createLayoutSlice.ts | 3 + .../src/shared/ui/DataTable/DataTable.tsx | 11 +- .../ui/DataTable/ExpansionPanelSection.tsx | 22 ---- .../src/shared/ui/DataTable/HeaderCell.tsx | 12 +- 6 files changed, 103 insertions(+), 62 deletions(-) delete mode 100644 packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionPanelSection.tsx diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx index 03d4bafaec..b9d3b188df 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx @@ -1,59 +1,105 @@ import Link from 'next/link' +import { useMemo } from 'react' import { LineGraphCell } from '@/loan/components/PageLlamaMarkets/cells' import { LlamaMarketColumnId } from '@/loan/components/PageLlamaMarkets/columns.enum' import { FavoriteMarketButton } from '@/loan/components/PageLlamaMarkets/FavoriteMarketButton' import { useUserMarketStats } from '@/loan/entities/llama-market-stats' +import useStore from '@/loan/store/useStore' import { ArrowRight } from '@carbon/icons-react' import Button from '@mui/material/Button' +import CardHeader from '@mui/material/CardHeader' +import Grid from '@mui/material/Grid2' import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' import { t } from '@ui-kit/lib/i18n' import { CopyIconButton } from '@ui-kit/shared/ui/CopyIconButton' -import { ExpansionPanelSection } from '@ui-kit/shared/ui/DataTable/ExpansionPanelSection' import { type ExpandedPanel } from '@ui-kit/shared/ui/DataTable/ExpansionRow' import { Metric } from '@ui-kit/shared/ui/Metric' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import type { LlamaMarket } from '../../entities/llama-markets' +const { Spacing } = SizesAndSpaces + +const GraphMobileCell = ({ market }: { market: LlamaMarket }) => { + const pageWidth = useStore((state) => state.layout.windowWidth) + const graphSize = useMemo(() => ({ width: pageWidth ? pageWidth - 40 : 300, height: 48 }), [pageWidth]) + return ( + + + {t`7D Rate Chart`} + + + + + ) +} + export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { original: market } }) => { const { data: earnings, error: earningsError } = useUserMarketStats(market, LlamaMarketColumnId.UserEarnings) const { data: deposited, error: depositedError } = useUserMarketStats(market, LlamaMarketColumnId.UserDeposited) const { leverage, utilizationPercent, liquidityUsd, userHasPosition, url, address, rates } = market return ( <> - - {t`Market Details`} - - - - - - } - > - - - - - {leverage > 0 && } - + + + + {t`Market Details`} + + + + + + } + sx={{ paddingInline: 0 }} + > + + + + + {leverage > 0 && ( + + + + )} + + + + + + + + + + {userHasPosition && ( - - {earnings?.earnings != null && } + + + + + {earnings?.earnings != null && ( + + + + )} {deposited?.deposited != null && ( - + + + )} - + )}
t.design.Layer[1].Fill, @@ -47,6 +42,7 @@ export const DataTable = ({ position: 'sticky', top: headerHeight, backgroundColor: t.design.Table.Header.Fill, + marginBlock: Sizing['sm'], })} data-testid="data-table-head" > @@ -60,6 +56,7 @@ export const DataTable = ({ header={header} isFirst={!index} isLast={index == headerGroup.headers.length - 1} + isSticky={!index && rowProps.shouldStickFirstColumn} /> ))} @@ -68,7 +65,7 @@ export const DataTable = ({ {table.getRowModel().rows.length === 0 && {emptyText}} {table.getRowModel().rows.map((row) => ( - key={row.id} row={row} sx={rowSx} expandedPanel={expandedPanel} /> + key={row.id} row={row} {...rowProps} /> ))}
diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/FilterRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/FilterRow.tsx index cb8a7ee7ac..1f4e0d52a4 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/FilterRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/FilterRow.tsx @@ -13,7 +13,7 @@ export const FilterRow = ({ children: ReactNode table: TanstackTable }) => ( - + count + headers.length, 0)} sx={(t) => ({ backgroundColor: t.design.Layer[1].Fill, padding: 0, borderBottomWidth: 0 })} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx index 60d030df9b..2832e8a1f4 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx @@ -12,10 +12,12 @@ export const HeaderCell = ({ header, isFirst, isLast, + isSticky, }: { header: Header isFirst: boolean isLast: boolean + isSticky: boolean }) => { const { column } = header const isSorted = column.getIsSorted() @@ -38,7 +40,13 @@ export const HeaderCell = ({ color: `text.highlight`, }, }), - ...(borderRight && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), + ...((borderRight || isSticky) && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), + ...(isSticky && { + position: 'sticky', + left: 0, + zIndex: 2, + backgroundColor: (t) => t.design.Table.Header.Fill, + }), }} colSpan={header.colSpan} onClick={column.getToggleSortingHandler()} diff --git a/packages/curve-ui-kit/src/themes/components/chip/mui-chip.ts b/packages/curve-ui-kit/src/themes/components/chip/mui-chip.ts index 107f9e2a82..63540ed488 100644 --- a/packages/curve-ui-kit/src/themes/components/chip/mui-chip.ts +++ b/packages/curve-ui-kit/src/themes/components/chip/mui-chip.ts @@ -16,9 +16,6 @@ const createColor = (color: keyof DesignSystem['Badges']['Fill'], Badges: Design props: { color: color.toLowerCase() as ChipProps['color'] }, }) -// note: the design system is using inverted themes for this color, there is no semantic colors for the clickable chips. -const invertPrimary = (color: DesignSystem['Color']) => color.Neutral[50] - const { Sizing, Spacing, IconSize } = SizesAndSpaces type ChipSizeDefinition = { @@ -30,7 +27,7 @@ type ChipSizeDefinition = { type ChipSizes = NonNullable const chipSizes: Record = { - extraSmall: { font: 'bodyXsBold', height: IconSize.md, iconSize: IconSize.sm }, + extraSmall: { font: 'bodyXsBold', height: IconSize.sm, iconSize: IconSize.sm }, small: { font: 'buttonXs', height: IconSize.md, iconSize: IconSize.sm }, medium: { font: 'buttonXs', height: Sizing.md, iconSize: IconSize.md }, large: { font: 'buttonM', height: Sizing.md, iconSize: IconSize.lg }, @@ -54,7 +51,7 @@ const chipSizeClickable: Record & { delet * - We do not use the "variant" prop (at the time of writing). */ export const defineMuiChip = ( - { Chips, Color, Text: { TextColors }, Layer, Badges }: DesignSystem, + { Chips, Text: { TextColors }, Badges }: DesignSystem, typography: TypographyOptions, ): Components['MuiChip'] => ({ styleOverrides: { diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index 36e361f9b1..fea2ca8e52 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -75,10 +75,7 @@ describe(`LlamaLend Markets`, () => { desktop: [174, 128], }[breakpoint] cy.get('[data-testid="table-filters"]').invoke('outerHeight').should('be.oneOf', filterHeight) - - // mobile row is usually 77px but can be higher when the text is long - const rowHeight = { mobile: [77, 88], tablet: [88], desktop: [88] }[breakpoint] - cy.get('[data-testid^="data-table-row"]').eq(10).invoke('outerHeight').should('be.oneOf', rowHeight) + cy.get('[data-testid^="data-table-row"]').eq(10).invoke('outerHeight').should('be.oneOf', 64) }) it('should sort', () => { @@ -113,11 +110,6 @@ describe(`LlamaLend Markets`, () => { const [green, red] = [isDarkMode ? '#32ce79' : '#167d4a', '#ed242f'] checkLineGraphColor('borrow', red) - if (breakpoint != 'mobile') { - showHiddenColumn({ element: 'line-graph-lend', toggle: 'lendChart' }) - } - checkLineGraphColor('lend', green) - // check that scrolling loads more snapshots: cy.get(`@lend-snapshots.all`, LOAD_TIMEOUT).then((calls1) => { cy.get('[data-testid^="data-table-row"]') From 81200247a21747fc1731b2669bc8e0414c6fd5b6 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 27 May 2025 11:30:54 +0200 Subject: [PATCH 04/13] fix: row separators not displayed --- .../curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx | 10 +++++++--- .../curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx index a10927bfd1..041f513e13 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx @@ -29,11 +29,14 @@ export const DataCell = ({ const { column, row } = cell const { variant, borderRight } = column.columnDef.meta ?? {} const children = flexRender(column.columnDef.cell, cell.getContext()) - const sx = { + + // with the collapse icon there is an extra wrapper + const wrapperSx = { textAlign: getAlignment(column), paddingInline: Spacing.sm, paddingBlock: Spacing.xs, // `md` removed, content should be vertically centered } + const showCollapseIcon = isMobile && isLast return ( ({ color="text.primary" component="td" sx={{ - ...(!showCollapseIcon && sx), + ...(!showCollapseIcon && wrapperSx), + borderBottom: (t) => `1px solid ${t.design.Layer[1].Outline}`, ...getExtraColumnPadding({ isFirst, isLast }), ...((borderRight || isSticky) && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), ...(isSticky && { @@ -55,7 +59,7 @@ export const DataCell = ({ > {showCollapseIcon ? ( - + {children} ({ `1px solid ${t.design.Layer[1].Outline}`, cursor: 'pointer', transition: `border-bottom ${TransitionFunction}`, [`& .${DesktopOnlyHoverClass}`]: { From 06e678e07dcf931c662cfb5e33c62b6312e55696 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 27 May 2025 12:45:33 +0200 Subject: [PATCH 05/13] feat: show last row on top of sticky header refactor: z-indexes --- .../src/shared/ui/DataTable/DataCell.tsx | 2 +- .../src/shared/ui/DataTable/DataRow.tsx | 9 ++ .../src/shared/ui/DataTable/DataTable.tsx | 84 ++++++++++--------- .../src/shared/ui/DataTable/HeaderCell.tsx | 2 +- .../src/shared/ui/DataTable/TableFilters.tsx | 2 +- .../src/themes/basic-theme/basic-theme.d.ts | 8 ++ .../src/themes/basic-theme/basic-theme.ts | 7 ++ .../cypress/e2e/loan/llamalend-markets.cy.ts | 2 +- .../cypress/support/helpers/lending-mocks.ts | 2 +- 9 files changed, 73 insertions(+), 45 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx index 041f513e13..9a28e9b352 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx @@ -51,7 +51,7 @@ export const DataCell = ({ ...(isSticky && { position: 'sticky', left: 0, - zIndex: 1, + zIndex: (t) => t.zIndex.tableStickyColumn, backgroundColor: (t) => t.design.Table.Row.Default, }), }} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index 63742b0189..172664a690 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -23,12 +23,14 @@ const onCellClick = (target: EventTarget, url: string, routerNavigate: (href: st export type DataRowProps = { row: Row + isLast: boolean expandedPanel: ExpandedPanel isMobile: boolean shouldStickFirstColumn: boolean } export const DataRow = ({ + isLast, row, expandedPanel, isMobile, @@ -57,6 +59,13 @@ export const DataRow = ({ }, '&:hover': { [`& .${DesktopOnlyHoverClass}`]: { opacity: { desktop: 1 } } }, [`&.${CypressHoverClass}`]: { [`& .${DesktopOnlyHoverClass}`]: { opacity: { desktop: 1 } } }, + ...(isLast && { + // to avoid the sticky header showing without any rows, show the last row on top of it + position: 'sticky', + zIndex: (t) => t.zIndex.tableStickyLastRow, + top: 0, + backgroundColor: (t) => t.design.Table.Row.Default, + }), }} ref={setElement} data-testid={element && `data-table-row-${row.id}`} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx index 27441f06d9..88983c49e1 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx @@ -28,45 +28,49 @@ export const DataTable = ({ emptyText: string children?: ReactNode // passed to minRowHeight?: number -} & Omit, 'row'>) => ( - t.design.Layer[1].Fill, - borderCollapse: 'separate' /* Don't collapse to avoid funky stuff with the sticky header */, - }} - data-testid="data-table" - > - ({ - zIndex: t.zIndex.appBar - 1, - position: 'sticky', - top: headerHeight, - backgroundColor: t.design.Table.Header.Fill, - marginBlock: Sizing['sm'], - })} - data-testid="data-table-head" +} & Omit, 'row' | 'isLast'>) => { + const { rows } = table.getRowModel() + const { shouldStickFirstColumn } = rowProps + return ( +
t.design.Layer[1].Fill, + borderCollapse: 'separate' /* Don't collapse to avoid funky stuff with the sticky header */, + }} + data-testid="data-table" > - {children && {children}} + ({ + zIndex: t.zIndex.tableHeader, + position: 'sticky', + top: headerHeight, + backgroundColor: t.design.Table.Header.Fill, + marginBlock: Sizing['sm'], + })} + data-testid="data-table-head" + > + {children && {children}} - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header, index) => ( - - ))} - - ))} - - - {table.getRowModel().rows.length === 0 && {emptyText}} - {table.getRowModel().rows.map((row) => ( - key={row.id} row={row} {...rowProps} /> - ))} - -
-) + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, index) => ( + + ))} + + ))} + + + {rows.length === 0 && {emptyText}} + {rows.map((row, index) => ( + key={row.id} row={row} isLast={index === rows.length - 1} {...rowProps} /> + ))} + + + ) +} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx index 2832e8a1f4..901bbc6697 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx @@ -44,7 +44,7 @@ export const HeaderCell = ({ ...(isSticky && { position: 'sticky', left: 0, - zIndex: 2, + zIndex: (t) => t.zIndex.tableHeaderStickyColumn, backgroundColor: (t) => t.design.Table.Header.Fill, }), }} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx index 38da715808..733d1bb561 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx @@ -81,7 +81,7 @@ export const TableFilters = ({ const settingsRef = useRef(null) const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) return ( - + {title} diff --git a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.d.ts b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.d.ts index 81cfbfa26b..75096a28a7 100644 --- a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.d.ts +++ b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.d.ts @@ -27,4 +27,12 @@ declare module '@mui/material/styles' { tertiary: string highlight: string } + + interface ZIndex { + tableStickyColumn: number + tableHeader: number + tableFilters: number + tableHeaderStickyColumn: number + tableStickyLastRow: number + } } diff --git a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts index d1a39e49b8..5af2485fa7 100644 --- a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts +++ b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts @@ -18,6 +18,13 @@ export const basicMuiTheme = createMuiTheme({ }, spacing: Object.values(Spacing), direction: 'ltr', + zIndex: { + tableStickyColumn: 100, // the sticky column in the table + tableFilters: 110, // the filters in the table header + tableHeader: 120, // the whole table header including filters + tableHeaderStickyColumn: 130, // the sticky column in the table header + tableStickyLastRow: 140, // the last row in the table is sticky so we don't show the header without any data + }, }) /** diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index fea2ca8e52..27a6e6fad9 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -75,7 +75,7 @@ describe(`LlamaLend Markets`, () => { desktop: [174, 128], }[breakpoint] cy.get('[data-testid="table-filters"]').invoke('outerHeight').should('be.oneOf', filterHeight) - cy.get('[data-testid^="data-table-row"]').eq(10).invoke('outerHeight').should('be.oneOf', 64) + cy.get('[data-testid^="data-table-row"]').eq(10).invoke('outerHeight').should('equal', 64) }) it('should sort', () => { diff --git a/tests/cypress/support/helpers/lending-mocks.ts b/tests/cypress/support/helpers/lending-mocks.ts index 432922086c..8bd59f346d 100644 --- a/tests/cypress/support/helpers/lending-mocks.ts +++ b/tests/cypress/support/helpers/lending-mocks.ts @@ -105,6 +105,6 @@ export const mockLendingVaults = (responses: LendingVaultResponses) => }) export const mockLendingSnapshots = () => - cy.intercept('https://prices.curve.finance/v1/lending/markets/*/*/snapshots?agg=none', { + cy.intercept('https://prices.curve.finance/v1/lending/markets/*/*/snapshots?agg=none&fetch_on_chain=false', { fixture: 'lending-snapshots.json', }) From d40d98d847842eaa898bf1410766b8731a9adc70 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 27 May 2025 13:05:28 +0200 Subject: [PATCH 06/13] refactor: hooks for useMediaQuery repetition --- .../components/ComboBoxSelectGauge/index.tsx | 4 +- .../ConfirmModal/ModalDialog.tsx | 4 +- .../TokensInPool/SelectTokenButton.tsx | 4 +- apps/main/src/dex/components/PoolLabel.tsx | 4 +- .../PageLlamaMarkets/LendingMarketsTable.tsx | 12 +-- .../PageLlamaMarkets/MarketsFilterChips.tsx | 6 +- .../loan/components/PageLlamaMarkets/Page.tsx | 5 +- .../cells/MarketTitleCell/MarketBadges.tsx | 3 +- .../cells/MarketTitleCell/MarketTitleCell.tsx | 86 +++++++++---------- .../PageLlamaMarkets/cells/RateCell.tsx | 4 +- .../curve-ui-kit/src/hooks/useBreakpoints.ts | 10 +++ .../src/shared/ui/DataTable/DataRow.tsx | 4 +- .../src/shared/ui/DataTable/TableFilters.tsx | 4 +- .../src/widgets/Header/Header.tsx | 9 +- packages/ui/src/Dialog/OpenDialogButton.tsx | 4 +- .../TableButtonFiltersMobile.tsx | 4 +- packages/ui/src/Tooltip/TooltipButton.tsx | 6 +- 17 files changed, 85 insertions(+), 88 deletions(-) create mode 100644 packages/curve-ui-kit/src/hooks/useBreakpoints.ts diff --git a/apps/main/src/dao/components/ComboBoxSelectGauge/index.tsx b/apps/main/src/dao/components/ComboBoxSelectGauge/index.tsx index 51cfe1ccbe..7dcac23a26 100644 --- a/apps/main/src/dao/components/ComboBoxSelectGauge/index.tsx +++ b/apps/main/src/dao/components/ComboBoxSelectGauge/index.tsx @@ -9,8 +9,8 @@ import { useUserGaugeWeightVotesQuery } from '@/dao/entities/user-gauge-weight-v import useStore from '@/dao/store/useStore' import { GaugeFormattedData } from '@/dao/types/dao.types' import { delayAction } from '@/dao/utils' -import useMediaQuery from '@mui/material/useMediaQuery' import ModalDialog from '@ui/Dialog' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { Chain } from '@ui-kit/utils/network' @@ -35,7 +35,7 @@ const ComboBoxGauges = ({ const setSelectedGauge = useStore((state) => state.gauges.setSelectedGauge) const setStateByKey = useStore((state) => state.gauges.setStateByKey) const gaugeMapper = useStore((state) => state.gauges.gaugeMapper) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const { data: userGaugeWeightVotes } = useUserGaugeWeightVotesQuery({ chainId: Chain.Ethereum, // DAO is only used on mainnet diff --git a/apps/main/src/dex/components/PageCreatePool/ConfirmModal/ModalDialog.tsx b/apps/main/src/dex/components/PageCreatePool/ConfirmModal/ModalDialog.tsx index 6f7c349eea..9e165a43d8 100644 --- a/apps/main/src/dex/components/PageCreatePool/ConfirmModal/ModalDialog.tsx +++ b/apps/main/src/dex/components/PageCreatePool/ConfirmModal/ModalDialog.tsx @@ -4,12 +4,12 @@ import type { AriaOverlayProps } from 'react-aria' import type { OverlayTriggerState } from 'react-stately' import styled from 'styled-components' import useStore from '@/dex/store/useStore' -import useMediaQuery from '@mui/material/useMediaQuery' import type { AriaDialogProps } from '@react-types/dialog' import Box from '@ui/Box' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton' import { breakpoints } from '@ui/utils/responsive' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' interface Props extends AriaOverlayProps, AriaDialogProps { footerContent?: ReactNode @@ -38,7 +38,7 @@ const ModalDialog = ({ const { titleProps } = useDialog(props, modalRef) usePreventScroll({ isDisabled: false }) // prevent scrolling while modal is open - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const isSmUp = useStore((state) => state.isSmUp) const { buttonProps: closeButtonProps } = useButton( diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectTokenButton.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectTokenButton.tsx index 01e39d0624..dc7e8660d9 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectTokenButton.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectTokenButton.tsx @@ -5,7 +5,6 @@ import { CreateToken } from '@/dex/components/PageCreatePool/types' import useStore from '@/dex/store/useStore' import { ChainId, CurveApi } from '@/dex/types/main.types' import { delayAction } from '@/dex/utils' -import useMediaQuery from '@mui/material/useMediaQuery' import { useButton } from '@react-aria/button' import { useFilter } from '@react-aria/i18n' import { useOverlayTriggerState } from '@react-stately/overlays' @@ -15,6 +14,7 @@ import Checkbox from '@ui/Checkbox' import Spinner, { SpinnerWrapper } from '@ui/Spinner' import { Chip } from '@ui/Typography' import { TokenSelectorModal } from '@ui-kit/features/select-token/ui/modal/TokenSelectorModal' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { TokenIcon } from '@ui-kit/shared/ui/TokenIcon' import { type Address, filterTokens, shortenAddress } from '@ui-kit/utils' @@ -46,7 +46,7 @@ const SelectTokenButton = ({ const { buttonProps: openButtonProps } = useButton({ onPress: () => overlayTriggerState.open() }, openButtonRef) const { endsWith } = useFilter({ sensitivity: 'base' }) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const nativeToken = useStore((state) => state.networks.nativeToken[chainId]) const userAddedTokens = useStore((state) => state.createPool.userAddedTokens) diff --git a/apps/main/src/dex/components/PoolLabel.tsx b/apps/main/src/dex/components/PoolLabel.tsx index d711d708a6..36dcff991b 100644 --- a/apps/main/src/dex/components/PoolLabel.tsx +++ b/apps/main/src/dex/components/PoolLabel.tsx @@ -7,11 +7,11 @@ import usePoolAlert from '@/dex/hooks/usePoolAlert' import useTokenAlert from '@/dex/hooks/useTokenAlert' import useStore from '@/dex/store/useStore' import { PoolData, PoolDataCache } from '@/dex/types/main.types' -import useMediaQuery from '@mui/material/useMediaQuery' import AlertBox from '@ui/AlertBox' import Box from '@ui/Box' import { TooltipAlert as AlertTooltipIcon } from '@ui/Tooltip' import { Chip } from '@ui/Typography' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { TokenIcons } from '@ui-kit/shared/ui/TokenIcons' type PoolListProps = { @@ -43,7 +43,7 @@ const PoolLabel = ({ className = '', blockchainId, isVisible = true, poolData, p const poolAlert = usePoolAlert(poolData?.pool.address, poolData?.hasVyperVulnerability) const tokenAlert = useTokenAlert(poolData?.tokenAddressesAll ?? []) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const searchedTerms = useStore((state) => state.poolList.searchedTerms) const { quickViewValue, onClick } = poolListProps ?? {} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx index c67404224a..cdad3a76d2 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx @@ -10,7 +10,6 @@ import { LendingMarketsFilters } from '@/loan/components/PageLlamaMarkets/Lendin import { LlamaMarketExpandedPanel } from '@/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel' import { MarketsFilterChips } from '@/loan/components/PageLlamaMarkets/MarketsFilterChips' import { type LlamaMarketsResult } from '@/loan/entities/llama-markets' -import { useMediaQuery } from '@mui/material' import Stack from '@mui/material/Stack' import { ExpandedState, @@ -21,6 +20,7 @@ import { SortingState, useReactTable, } from '@tanstack/react-table' +import { useIsMobile, useIsTablet } from '@ui-kit/hooks/useBreakpoints' import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString' import { t } from '@ui-kit/lib/i18n' import { DataTable } from '@ui-kit/shared/ui/DataTable' @@ -38,11 +38,10 @@ const { Spacing, MaxWidth, Sizing } = SizesAndSpaces */ const useVisibility = (sorting: SortingState, hasPositions: boolean | undefined) => { const sortField = (sorting.length ? sorting : DEFAULT_SORT)[0].id as LlamaMarketColumnId - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) const groups = useMemo(() => createLlamaMarketsColumnOptions(hasPositions), [hasPositions]) const visibilitySettings = useVisibilitySettings(groups) const columnVisibility = useMemo(() => createLlamaMarketsMobileColumns(sortField), [sortField]) - return { sortField, ...visibilitySettings, ...(isMobile && { columnVisibility }) } + return { sortField, ...visibilitySettings, ...(useIsMobile() && { columnVisibility }) } } // todo: rename to LlamaMarketsTable @@ -79,10 +78,6 @@ export const LendingMarketsTable = ({ maxMultiSortColCount: 3, // allow 3 columns to be sorted at once while holding shift }) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) - const isTablet = useMediaQuery((t) => t.breakpoints.down('desktop')) && !isMobile - const shouldStickFirstColumn = isTablet && !!hasPositions - return ( title={t`Llamalend Markets`} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/MarketsFilterChips.tsx b/apps/main/src/loan/components/PageLlamaMarkets/MarketsFilterChips.tsx index e7353ddac9..5694937e7a 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/MarketsFilterChips.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/MarketsFilterChips.tsx @@ -8,7 +8,7 @@ import PersonIcon from '@mui/icons-material/Person' import Grid from '@mui/material/Grid2' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' -import useMediaQuery from '@mui/material/useMediaQuery' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { HeartIcon } from '@ui-kit/shared/icons/HeartIcon' import { PointsIcon } from '@ui-kit/shared/icons/PointsIcon' @@ -83,8 +83,6 @@ export const MarketsFilterChips = ({ const [rewards, toggleRewards] = useToggleFilter(LlamaMarketColumnId.Rewards, props) const [marketTypes, toggleMarkets] = useMarketTypeFilter(props) const { address } = useAccount() - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) - return ( - + {t`Hidden Markets`} {hiddenMarketCount} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx b/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx index a61f22feb4..7595c913c8 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/Page.tsx @@ -14,11 +14,11 @@ import { import { useLlamaMarkets } from '@/loan/entities/llama-markets' import { invalidateAllUserMintMarkets, invalidateMintMarkets, setMintMarkets } from '@/loan/entities/mint-markets' import useStore from '@/loan/store/useStore' -import { useMediaQuery } from '@mui/material' import Box from '@mui/material/Box' import Skeleton from '@mui/material/Skeleton' import { useUserProfileStore } from '@ui-kit/features/user-profile' import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store' +import { useIsTiny } from '@ui-kit/hooks/useBreakpoints' import { logSuccess } from '@ui-kit/lib' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { Address } from '@ui-kit/utils' @@ -68,13 +68,12 @@ export const LlamaMarketsPage = (props: CrvUsdServerData) => { const { address } = useAccount() const { data, isError, isLoading } = useLlamaMarkets(address) const minLiquidity = useUserProfileStore((s) => s.hideSmallPools) ? SMALL_POOL_TVL : 0 - const isTiny = useMediaQuery('(max-width:400px)') const bannerHeight = useStore((state) => state.layout.height.globalAlert) const headerHeight = useHeaderHeight(bannerHeight) const showSkeleton = !data && (!isError || isLoading) // on initial render isLoading is still false return ( - + {showSkeleton ? ( ) : ( diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx index 3ca1c97df6..e5454c911f 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx @@ -7,6 +7,7 @@ import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' import useMediaQuery from '@mui/material/useMediaQuery' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { ExclamationTriangleIcon } from '@ui-kit/shared/icons/ExclamationTriangleIcon' import { RewardIcons } from '@ui-kit/shared/ui/RewardIcon' @@ -28,7 +29,7 @@ const poolTypeTooltips: Record string> = { /** Displays badges for a pool, such as the chain icon and the pool type. */ export const MarketBadges = ({ market }: { market: LlamaMarket }) => { const { address, rewards, type, leverage, deprecatedMessage } = market - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const isSmall = useMediaQuery('(max-width:1250px)') const { isCollateralEroded } = useUserMarketStats(market)?.data ?? {} return ( diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx index 8dffc55be5..fc235eb40a 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx @@ -5,8 +5,8 @@ import { LlamaMarket } from '@/loan/entities/llama-markets' import MuiLink from '@mui/material/Link' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' -import useMediaQuery from '@mui/material/useMediaQuery' import { CellContext } from '@tanstack/react-table' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { CopyIconButton } from '@ui-kit/shared/ui/CopyIconButton' import { ClickableInRowClass, DesktopOnlyHoverClass } from '@ui-kit/shared/ui/DataTable' @@ -15,47 +15,45 @@ import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' const { Spacing } = SizesAndSpaces -export const MarketTitleCell = ({ row: { original: market } }: CellContext) => ( - - - - t.breakpoints.down('tablet')) ? 'tableCellMBold' : 'tableCellL'} - direction="row" - gap={2} - > - t.breakpoints.down('tablet')) && { - // cancel click on mobile so the panel can open, there is a separate button for it - onClick: (e: MouseEvent) => e.preventDefault(), - })} - sx={{ - // for very small screens, truncate the text and limit to a maximum width - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - maxWidth: '40vw', // adjust as needed - }} - > - {market.assets.collateral.symbol} - {market.assets.borrowed.symbol} - - - - +export const MarketTitleCell = ({ row: { original: market } }: CellContext) => { + const isMobile = useIsMobile() + return ( + + + + + ) => e.preventDefault(), + })} + sx={{ + // for very small screens, truncate the text and limit to a maximum width + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + maxWidth: '40vw', // adjust as needed + }} + > + {market.assets.collateral.symbol} - {market.assets.borrowed.symbol} + + + + + - -) + ) +} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx index 0754132b23..795112e693 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/RateCell.tsx @@ -3,7 +3,7 @@ import { LlamaMarket } from '@/loan/entities/llama-markets' import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' -import useMediaQuery from '@mui/material/useMediaQuery' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import useIntersectionObserver from '@ui-kit/hooks/useIntersectionObserver' import { t } from '@ui-kit/lib/i18n' import { RewardIcons } from '@ui-kit/shared/ui/RewardIcon' @@ -22,7 +22,7 @@ export const RateCell = ({ market, type }: { market: LlamaMarket; type: RateType const { rewards, type: marketType } = market const rewardsAction = getRewardsAction(marketType, type) const poolRewards = rewards.filter(({ action }) => action == rewardsAction) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() return ( theme.breakpoints.up('desktop') +const isTabletDown = (theme: Theme) => theme.breakpoints.down('tablet') + +export const useIsTiny = () => useMediaQuery('(max-width:400px)') +export const useIsMobile = () => useMediaQuery(isTabletDown) +export const useIsDesktop = () => useMediaQuery(isDesktopUp) +export const useIsTablet = () => ![useIsDesktop(), useIsMobile()].includes(true) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index 172664a690..4c97912819 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -2,6 +2,7 @@ import { useRouter } from 'next/navigation' import { type MouseEvent, useCallback, useState } from 'react' import TableRow from '@mui/material/TableRow' import { type Row } from '@tanstack/react-table' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { type ExpandedPanel, ExpansionRow } from '@ui-kit/shared/ui/DataTable/ExpansionRow' import { TransitionFunction } from '@ui-kit/themes/design/0_primitives' import { CypressHoverClass, hasParentWithClass } from '@ui-kit/utils/dom' @@ -25,7 +26,6 @@ export type DataRowProps = { row: Row isLast: boolean expandedPanel: ExpandedPanel - isMobile: boolean shouldStickFirstColumn: boolean } @@ -33,9 +33,9 @@ export const DataRow = ({ isLast, row, expandedPanel, - isMobile, shouldStickFirstColumn, }: DataRowProps) => { + const isMobile = useIsMobile() const [element, setElement] = useState(null) // note: useRef doesn't get updated in cypress const { push } = useRouter() const url = row.original.url diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx index 733d1bb561..2080a0d39c 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx @@ -1,5 +1,4 @@ import { forwardRef, ReactNode, useCallback, useMemo, useRef, useState } from 'react' -import { useMediaQuery } from '@mui/material' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Collapse from '@mui/material/Collapse' @@ -10,6 +9,7 @@ import Stack from '@mui/material/Stack' import SvgIcon from '@mui/material/SvgIcon' import Typography from '@mui/material/Typography' import { ColumnFiltersState } from '@tanstack/react-table' +import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { useFilterExpanded } from '@ui-kit/hooks/useLocalStorage' import { useSwitch } from '@ui-kit/hooks/useSwitch' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' @@ -79,7 +79,7 @@ export const TableFilters = ({ const [filterExpanded, setFilterExpanded] = useFilterExpanded(title) const [visibilitySettingsOpen, openVisibilitySettings, closeVisibilitySettings] = useSwitch() const settingsRef = useRef(null) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() return ( diff --git a/packages/curve-ui-kit/src/widgets/Header/Header.tsx b/packages/curve-ui-kit/src/widgets/Header/Header.tsx index eb4b5a1b85..6a92491ff8 100644 --- a/packages/curve-ui-kit/src/widgets/Header/Header.tsx +++ b/packages/curve-ui-kit/src/widgets/Header/Header.tsx @@ -1,20 +1,17 @@ import { usePathname } from 'next/navigation' import { useMemo } from 'react' -import type { Theme } from '@mui/material' import { useTheme } from '@mui/material/styles' -import useMediaQuery from '@mui/material/useMediaQuery' import { WalletToast } from '@ui-kit/features/connect-wallet' import { WagmiConnectModal } from '@ui-kit/features/connect-wallet/ui/WagmiConnectModal' +import { useIsDesktop } from '@ui-kit/hooks/useBreakpoints' import { useBetaFlag } from '@ui-kit/hooks/useLocalStorage' import { routeToPage } from '@ui-kit/shared/routes' import { DESKTOP_HEADER_HEIGHT, DesktopHeader } from './DesktopHeader' import { calcMobileHeaderHeight, MobileHeader } from './MobileHeader' import { HeaderProps } from './types' -const isDesktopQuery = (theme: Theme) => theme.breakpoints.up('desktop') - export const Header = ({ routes, ...props }: HeaderProps) => { - const isDesktop = useMediaQuery(isDesktopQuery, { noSsr: true }) + const isDesktop = useIsDesktop() const [isBeta] = useBetaFlag() const pathname = usePathname() const { networkId, height } = props @@ -38,7 +35,7 @@ export const Header = ({ routes, ...props }: HeaderProp * Helper function to calculate the header height based on the banner height and the current screen size */ export const useHeaderHeight = (bannerHeight: number | undefined) => { - const isDesktop = useMediaQuery(isDesktopQuery, { noSsr: true }) + const isDesktop = useIsDesktop() const theme = useTheme() const headerHeight = isDesktop ? DESKTOP_HEADER_HEIGHT : calcMobileHeaderHeight(theme) return `calc(${headerHeight} + ${bannerHeight ?? 0}px)` diff --git a/packages/ui/src/Dialog/OpenDialogButton.tsx b/packages/ui/src/Dialog/OpenDialogButton.tsx index 6c632c2351..90f40e6781 100644 --- a/packages/ui/src/Dialog/OpenDialogButton.tsx +++ b/packages/ui/src/Dialog/OpenDialogButton.tsx @@ -1,3 +1,4 @@ +import { useIsMobile } from 'curve-ui-kit/src/hooks/useBreakpoints' import { ReactNode, useRef } from 'react' import { useButton } from 'react-aria' import styled from 'styled-components' @@ -5,7 +6,6 @@ import Button from 'ui/src/Button' import type { ButtonProps } from 'ui/src/Button/types' import Icon from 'ui/src/Icon/Icon' import { delayAction } from 'ui/src/utils/helpers' -import useMediaQuery from '@mui/material/useMediaQuery' import type { OverlayTriggerState } from '@react-stately/overlays' interface OpenDialogButtonProps extends ButtonProps { @@ -23,7 +23,7 @@ const OpenDialogButton = ({ ...props }: OpenDialogButtonProps) => { const openButtonRef = useRef(null) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const { buttonProps } = useButton( { onPress: () => (isMobile ? delayAction(overlayTriggerState.open) : overlayTriggerState.open()) }, openButtonRef, diff --git a/packages/ui/src/TableButtonFiltersMobile/TableButtonFiltersMobile.tsx b/packages/ui/src/TableButtonFiltersMobile/TableButtonFiltersMobile.tsx index e1207938dd..89ae5a3d2d 100644 --- a/packages/ui/src/TableButtonFiltersMobile/TableButtonFiltersMobile.tsx +++ b/packages/ui/src/TableButtonFiltersMobile/TableButtonFiltersMobile.tsx @@ -1,8 +1,8 @@ +import { useIsMobile } from 'curve-ui-kit/src/hooks/useBreakpoints' import { useMemo } from 'react' import ModalDialog, { OpenDialogButton } from 'ui/src/Dialog' import { RadioGroup } from 'ui/src/Radio' import { delayAction } from 'ui/src/utils/helpers' -import useMediaQuery from '@mui/material/useMediaQuery' import { useOverlayTriggerState } from '@react-stately/overlays' import TableButtonFiltersMobileItem from './components/TableButtonFiltersMobileItem' import TableButtonFiltersMobileItemIcon from './components/TableButtonFiltersMobileItemIcon' @@ -21,7 +21,7 @@ const TableButtonFiltersMobile = ({ updateRouteFilterKey(filterKey: string): void }) => { const overlayTriggerState = useOverlayTriggerState({}) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const handleClose = () => { if (isMobile) { diff --git a/packages/ui/src/Tooltip/TooltipButton.tsx b/packages/ui/src/Tooltip/TooltipButton.tsx index a29759aaab..817ddbaf29 100644 --- a/packages/ui/src/Tooltip/TooltipButton.tsx +++ b/packages/ui/src/Tooltip/TooltipButton.tsx @@ -1,13 +1,13 @@ +import { useIsMobile } from 'curve-ui-kit/src/hooks/useBreakpoints' import { MouseEvent, ReactNode, useCallback, useRef, useState } from 'react' import { useTooltipTrigger } from 'react-aria' -import { useTooltipTriggerState } from 'react-stately' import type { TooltipTriggerProps } from 'react-stately' +import { useTooltipTriggerState } from 'react-stately' import styled from 'styled-components' import Icon from 'ui/src/Icon' import Tooltip from 'ui/src/Tooltip/Tooltip' import type { TooltipProps } from 'ui/src/Tooltip/types' import { breakpoints } from 'ui/src/utils' -import useMediaQuery from '@mui/material/useMediaQuery' export type IconStyles = { $svgTop?: string } @@ -34,7 +34,7 @@ function TooltipButton({ iconStyles?: IconStyles }) { const state = useTooltipTriggerState({ delay: 0, ...props }) - const isMobile = useMediaQuery((t) => t.breakpoints.down('tablet')) + const isMobile = useIsMobile() const ref = useRef(null) const { triggerProps, tooltipProps } = useTooltipTrigger(props, state, ref) From c3a5323ed46297b1990c506b1f7a543576a60701 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 27 May 2025 15:47:29 +0200 Subject: [PATCH 07/13] fix: tiny screens adjustments --- .../LlamaMarketExpandedPanel.tsx | 28 ++++++++++--------- .../src/shared/ui/DataTable/TableFilters.tsx | 5 ++-- .../src/widgets/Footer/Footer.tsx | 4 ++- .../src/widgets/Footer/Section.tsx | 5 ++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx index b9d3b188df..2e4e82c91d 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx @@ -11,6 +11,7 @@ import CardHeader from '@mui/material/CardHeader' import Grid from '@mui/material/Grid2' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' +import { useIsTiny } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { CopyIconButton } from '@ui-kit/shared/ui/CopyIconButton' import { type ExpandedPanel } from '@ui-kit/shared/ui/DataTable/ExpansionRow' @@ -20,24 +21,17 @@ import type { LlamaMarket } from '../../entities/llama-markets' const { Spacing } = SizesAndSpaces -const GraphMobileCell = ({ market }: { market: LlamaMarket }) => { +function useMobileGraphSize() { const pageWidth = useStore((state) => state.layout.windowWidth) - const graphSize = useMemo(() => ({ width: pageWidth ? pageWidth - 40 : 300, height: 48 }), [pageWidth]) - return ( - - - {t`7D Rate Chart`} - - - - - ) + const isTiny = useIsTiny() + return useMemo(() => ({ width: pageWidth ? pageWidth - (isTiny ? 20 : 40) : 300, height: 48 }), [pageWidth, isTiny]) } export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { original: market } }) => { const { data: earnings, error: earningsError } = useUserMarketStats(market, LlamaMarketColumnId.UserEarnings) const { data: deposited, error: depositedError } = useUserMarketStats(market, LlamaMarketColumnId.UserDeposited) const { leverage, utilizationPercent, liquidityUsd, userHasPosition, url, address, rates } = market + const graphSize = useMobileGraphSize() return ( <> @@ -80,8 +74,16 @@ export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { or - - + + + + {t`7D Rate Chart`} + + + {/**/} + + {/**/} + {userHasPosition && ( diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx index 2080a0d39c..17dd38bbf0 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx @@ -9,7 +9,7 @@ import Stack from '@mui/material/Stack' import SvgIcon from '@mui/material/SvgIcon' import Typography from '@mui/material/Typography' import { ColumnFiltersState } from '@tanstack/react-table' -import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' +import { useIsMobile, useIsTiny } from '@ui-kit/hooks/useBreakpoints' import { useFilterExpanded } from '@ui-kit/hooks/useLocalStorage' import { useSwitch } from '@ui-kit/hooks/useSwitch' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' @@ -80,8 +80,9 @@ export const TableFilters = ({ const [visibilitySettingsOpen, openVisibilitySettings, closeVisibilitySettings] = useSwitch() const settingsRef = useRef(null) const isMobile = useIsMobile() + const maxWidth = `calc(100vw${useIsTiny() ? '' : ' - 20px'})` // in tiny screens we remove the table margins completely return ( - + {title} diff --git a/packages/curve-ui-kit/src/widgets/Footer/Footer.tsx b/packages/curve-ui-kit/src/widgets/Footer/Footer.tsx index bdddfc2c88..45dfae14b0 100644 --- a/packages/curve-ui-kit/src/widgets/Footer/Footer.tsx +++ b/packages/curve-ui-kit/src/widgets/Footer/Footer.tsx @@ -2,6 +2,7 @@ import Box from '@mui/material/Box' import Grid from '@mui/material/Grid2' import { styled } from '@mui/material/styles' import { LlamaImg } from '@ui/images' +import { useIsTiny } from '@ui-kit/hooks/useBreakpoints' import { useBetaFlag } from '@ui-kit/hooks/useLocalStorage' import { useSwitch } from '@ui-kit/hooks/useSwitch' import { AppName } from '@ui-kit/shared/routes' @@ -28,6 +29,7 @@ export const Footer = ({ appName, networkId, headerHeight }: FooterProps) => { const [isBetaModalOpen, openBetaModal, closeBetaModal] = useSwitch() const [isBetaSnackbarVisible, openBetaSnackbar, closeBetaSnackbar] = useSwitch() const [isBeta, setIsBeta] = useBetaFlag() + const isTiny = useIsTiny() return ( { desktop: 3, }} > -
+
))} diff --git a/packages/curve-ui-kit/src/widgets/Footer/Section.tsx b/packages/curve-ui-kit/src/widgets/Footer/Section.tsx index cade19f642..50bb745e1a 100644 --- a/packages/curve-ui-kit/src/widgets/Footer/Section.tsx +++ b/packages/curve-ui-kit/src/widgets/Footer/Section.tsx @@ -9,9 +9,10 @@ export type SectionProps = { links: Omit[] networkId: string appName: AppName + isTiny: boolean } -export const Section = ({ title, links, networkId, appName }: SectionProps) => ( +export const Section = ({ title, links, networkId, appName, isTiny }: SectionProps) => ( ( {links.map((link) => ( - + ))} From 8a10c4dc575dae3acc586c70884372f33e238ca6 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 28 May 2025 09:37:28 +0200 Subject: [PATCH 08/13] fix: table hover --- packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx | 7 ++++++- packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index 4c97912819..a9099c92fc 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -57,7 +57,12 @@ export const DataRow = ({ opacity: { mobile: 1, desktop: 0 }, transition: `opacity ${TransitionFunction}`, }, - '&:hover': { [`& .${DesktopOnlyHoverClass}`]: { opacity: { desktop: 1 } } }, + '&:hover': { + [`& .${DesktopOnlyHoverClass}`]: { opacity: { desktop: 1 } }, + '& td, & th': { + backgroundColor: (t) => t.design.Table.Row.Hover, + }, + }, [`&.${CypressHoverClass}`]: { [`& .${DesktopOnlyHoverClass}`]: { opacity: { desktop: 1 } } }, ...(isLast && { // to avoid the sticky header showing without any rows, show the last row on top of it diff --git a/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx b/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx index 8d1fd865d8..739224845e 100644 --- a/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx +++ b/packages/curve-ui-kit/src/shared/ui/InvertOnHover.tsx @@ -33,7 +33,7 @@ type InvertOnHoverProps = { } /** - * A component that inverts the theme when hovered. Currently disabled due to performance issues. + * A component that inverts the theme when hovered. Currently, disabled due to performance issues. * In the future we want to use css variables to achieve the same effect without fully changing the theme. */ export const InvertOnHover = ({ children }: InvertOnHoverProps) => children From 81ff7892a0afadc946fb38b774901c33a062814f Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 28 May 2025 13:03:22 +0200 Subject: [PATCH 09/13] feat: mobile row 64px --- .../cells/MarketTitleCell/MarketBadges.tsx | 8 +-- .../cells/MarketTitleCell/MarketTitleCell.tsx | 7 ++- .../src/shared/ui/DataTable/DataCell.tsx | 8 ++- .../src/shared/ui/DataTable/DataRow.tsx | 3 + .../TableVisibilitySettingsPopover.tsx | 60 ++++++++++--------- .../src/themes/basic-theme/basic-theme.ts | 10 +++- .../cypress/e2e/loan/llamalend-markets.cy.ts | 47 ++++----------- 7 files changed, 68 insertions(+), 75 deletions(-) diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx index e5454c911f..fa6690e24b 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketBadges.tsx @@ -7,14 +7,13 @@ import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' import useMediaQuery from '@mui/material/useMediaQuery' -import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { t } from '@ui-kit/lib/i18n' import { ExclamationTriangleIcon } from '@ui-kit/shared/icons/ExclamationTriangleIcon' import { RewardIcons } from '@ui-kit/shared/ui/RewardIcon' import { Tooltip } from '@ui-kit/shared/ui/Tooltip' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' -const { Spacing } = SizesAndSpaces +const { Spacing, Sizing } = SizesAndSpaces const poolTypeNames: Record string> = { [LlamaMarketType.Lend]: () => t`Lend`, @@ -27,13 +26,12 @@ const poolTypeTooltips: Record string> = { } /** Displays badges for a pool, such as the chain icon and the pool type. */ -export const MarketBadges = ({ market }: { market: LlamaMarket }) => { +export const MarketBadges = ({ market, isMobile }: { market: LlamaMarket; isMobile: boolean }) => { const { address, rewards, type, leverage, deprecatedMessage } = market - const isMobile = useIsMobile() const isSmall = useMediaQuery('(max-width:1250px)') const { isCollateralEroded } = useUserMarketStats(market)?.data ?? {} return ( - + ) => { const isMobile = useIsMobile() return ( - + - + {market.assets.collateral.symbol} - {market.assets.borrowed.symbol} @@ -52,7 +53,7 @@ export const MarketTitleCell = ({ row: { original: market } }: CellContext - + ) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx index 9a28e9b352..ae58255259 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx @@ -1,3 +1,4 @@ +import { mapValues } from 'lodash' import { Stack } from '@mui/material' import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' @@ -30,11 +31,12 @@ export const DataCell = ({ const { variant, borderRight } = column.columnDef.meta ?? {} const children = flexRender(column.columnDef.cell, cell.getContext()) - // with the collapse icon there is an extra wrapper + // with the collapse icon there is an extra wrapper, so keep the sx separate const wrapperSx = { textAlign: getAlignment(column), paddingInline: Spacing.sm, - paddingBlock: Spacing.xs, // `md` removed, content should be vertically centered + // 1px less for the border bottom + paddingBlock: mapValues({ ...Spacing.xs, mobile: Spacing.md.mobile }, (value) => `${value} calc(${value} - 1px)`), } const showCollapseIcon = isMobile && isLast @@ -45,7 +47,6 @@ export const DataCell = ({ component="td" sx={{ ...(!showCollapseIcon && wrapperSx), - borderBottom: (t) => `1px solid ${t.design.Layer[1].Outline}`, ...getExtraColumnPadding({ isFirst, isLast }), ...((borderRight || isSticky) && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), ...(isSticky && { @@ -54,6 +55,7 @@ export const DataCell = ({ zIndex: (t) => t.zIndex.tableStickyColumn, backgroundColor: (t) => t.design.Table.Row.Default, }), + borderBlockEnd: (t) => `1px solid ${t.design.Layer[1].Outline}`, }} data-testid={`data-table-cell-${column.id}`} > diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index a9099c92fc..ee319d8f57 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -5,11 +5,14 @@ import { type Row } from '@tanstack/react-table' import { useIsMobile } from '@ui-kit/hooks/useBreakpoints' import { type ExpandedPanel, ExpansionRow } from '@ui-kit/shared/ui/DataTable/ExpansionRow' import { TransitionFunction } from '@ui-kit/themes/design/0_primitives' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { CypressHoverClass, hasParentWithClass } from '@ui-kit/utils/dom' import { InvertOnHover } from '../InvertOnHover' import { ClickableInRowClass, DesktopOnlyHoverClass, type TableItem } from './data-table.utils' import { DataCell } from './DataCell' +const { Sizing } = SizesAndSpaces + const onCellClick = (target: EventTarget, url: string, routerNavigate: (href: string) => void) => { // ignore clicks on elements that should be clickable inside the row if (hasParentWithClass(target, ClickableInRowClass, { untilTag: 'TR' })) { diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx index 7340c9711e..8a7d7acc36 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx @@ -46,35 +46,37 @@ export const TableVisibilitySettingsPopover = ({ }} > - {visibilityGroups.map(({ options, label }) => ( - - `1px solid ${t.design.Layer[1].Outline}` }} - > - {label} - - {options - .filter((option) => option.label) - .map( - ({ columns, active, label, enabled }) => - enabled && ( - toggleVisibility(columns)} - size="small" - /> - } - label={label} - /> - ), - )} - - ))} + {visibilityGroups + .filter(({ options }) => options.some((o) => o.label)) + .map(({ options, label }) => ( + + `1px solid ${t.design.Layer[1].Outline}` }} + > + {label} + + {options + .filter((option) => option.label) + .map( + ({ columns, active, label, enabled }) => + enabled && ( + toggleVisibility(columns)} + size="small" + /> + } + label={label} + /> + ), + )} + + ))} ) diff --git a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts index 5af2485fa7..90e7e5d46f 100644 --- a/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts +++ b/packages/curve-ui-kit/src/themes/basic-theme/basic-theme.ts @@ -27,6 +27,8 @@ export const basicMuiTheme = createMuiTheme({ }, }) +export type Responsive = Record + /** * Create a responsive object based on the breakpoints defined in the basicMuiTheme. * @@ -57,4 +59,10 @@ export const handleBreakpoints = (values: Record +export const mapBreakpoints = ( + values: Responsive, + callback: (value: string, breakpoint: Breakpoint) => CSSObject, +): CSSObject => + Object.fromEntries( + Object.entries(values).map(([breakpoint, value]) => [breakpoint, callback(value, breakpoint as Breakpoint)]), + ) diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index 27a6e6fad9..949095351b 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -14,18 +14,15 @@ import { assertInViewport, assertNotInViewport, type Breakpoint, - checkIsDarkMode, hideDomainBanner, LOAD_TIMEOUT, oneDesktopViewport, - oneMobileViewport, oneViewport, RETRY_IN_CI, } from '@/support/ui' import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store' describe(`LlamaLend Markets`, () => { - let isDarkMode: boolean let breakpoint: Breakpoint let width: number, height: number let vaultData: LendingVaultResponses @@ -46,7 +43,6 @@ describe(`LlamaLend Markets`, () => { cy.visit('/crvusd/ethereum/beta-markets/', { onBeforeLoad: (window) => { window.localStorage.clear() - isDarkMode = checkIsDarkMode(window) hideDomainBanner(window) }, ...LOAD_TIMEOUT, @@ -55,11 +51,7 @@ describe(`LlamaLend Markets`, () => { }) const firstRow = () => cy.get(`[data-testid^="data-table-row-"]`).eq(0) - const copyFirstAddress = () => cy.get(`[data-testid^="copy-market-address"]:visible`).first() - it('should have sticky headers', () => { - cy.viewport(...oneMobileViewport()) - const breakpoint = 'mobile' cy.get('[data-testid^="data-table-row"]').last().then(assertNotInViewport) cy.get('[data-testid^="data-table-row"]').eq(10).scrollIntoView() cy.get('[data-testid="data-table-head"] th').eq(1).then(assertInViewport) @@ -71,8 +63,8 @@ describe(`LlamaLend Markets`, () => { mobile: [194, 180, 156, 144], // on tablet, we expect always 3 rows to fit all market filter chips tablet: [174], - // on desktop, we expect 2 rows as the chips should always fit. The original design was 128 px. - desktop: [174, 128], + // on desktop, we have an extra row until 1450px + desktop: [182, 128], }[breakpoint] cy.get('[data-testid="table-filters"]').invoke('outerHeight').should('be.oneOf', filterHeight) cy.get('[data-testid^="data-table-row"]').eq(10).invoke('outerHeight').should('equal', 64) @@ -106,9 +98,7 @@ describe(`LlamaLend Markets`, () => { cy.get(`[data-testid="pool-type-mint"]`).should('not.exist') }) expandFirstRowOnMobile() - - const [green, red] = [isDarkMode ? '#32ce79' : '#167d4a', '#ed242f'] - checkLineGraphColor('borrow', red) + checkLineGraphColor('borrow', '#ed242f') // check that scrolling loads more snapshots: cy.get(`@lend-snapshots.all`, LOAD_TIMEOUT).then((calls1) => { @@ -193,10 +183,11 @@ describe(`LlamaLend Markets`, () => { it('should allow filtering favorites', { scrollBehavior: false }, () => { expandFirstRowOnMobile() if (breakpoint == 'desktop') { - // on desktop, favorite icon is only visible on hover - firstRow().trigger('mouseenter', { waitForAnimations: true, scrollBehavior: false, force: true }) + // on desktop, the favorite icon is not visible until hovered - but cypress doesn't support that so use force + cy.get(`[data-testid="favorite-icon"]`).first().click({ force: true }) + } else { + cy.get(`[data-testid="favorite-icon"]:visible`).first().click() } - cy.get(`[data-testid="favorite-icon"]:visible`).first().click() withFilterChips(() => cy.get(`[data-testid="chip-favorites"]`).click()) cy.get(`[data-testid^="data-table-row"]`).should('have.length', 1) cy.get(`[data-testid="favorite-icon"]:visible`).should('not.exist') @@ -218,21 +209,16 @@ describe(`LlamaLend Markets`, () => { }), )) - it(`should hover and copy the market address`, RETRY_IN_CI, () => { + it(`should copy the market address`, RETRY_IN_CI, () => { if (breakpoint === 'mobile') { expandFirstRowOnMobile() + } + if (breakpoint === 'desktop') { + // on desktop, the copy button is not visible until hovered - but cypress doesn't support that so use force + cy.get(`[data-testid^="copy-market-address"]`).first().click({ force: true }) } else { - const hoverBackground = isDarkMode ? 'rgb(254, 250, 239)' : 'rgb(37, 36, 32)' - cy.get(`[data-testid^="copy-market-address"]`).should('have.css', 'opacity', breakpoint === 'desktop' ? '0' : '1') - firstRow().should('not.have.css', 'background-color', hoverBackground) - cy.scrollTo(0, 0) - firstRow().trigger('mouseenter', { waitForAnimations: true, scrollBehavior: false, force: true }) - firstRow().should('have.css', 'background-color', hoverBackground) - copyFirstAddress().should('have.css', 'opacity', '1') + cy.get(`[data-testid^="copy-market-address"]:visible`).first().click() } - // todo: this test fails sometimes in ci because the click doesn't work - copyFirstAddress().click() - copyFirstAddress().click() // click again, in chrome in CI the first click doesn't work (because of tooltip?) cy.get(`[data-testid="copy-confirmation"]`).should('be.visible') }) @@ -332,10 +318,3 @@ const selectCoin = (symbol: string, type: TokenType) => { cy.get('body').click(0, 0) // close popover cy.get(`[data-testid="data-table-cell-assets"] [data-testid^="token-icon-${symbol}"]`).should('be.visible') } - -function showHiddenColumn({ element, toggle }: { element: string; toggle: string }) { - cy.get(`[data-testid="${element}"]`).should('not.exist') - cy.get(`[data-testid="btn-visibility-settings"]`).click() - cy.get(`[data-testid="visibility-toggle-${toggle}"]`).click() - cy.get(`[data-testid="${element}"]`).should('be.visible') -} From 12f2a42a923b654326541a1b507117f172cab1da Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 30 May 2025 09:37:36 +0200 Subject: [PATCH 10/13] fix: e2e for chromium --- tests/cypress/e2e/loan/llamalend-markets.cy.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index 949095351b..18b4593250 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -213,12 +213,13 @@ describe(`LlamaLend Markets`, () => { if (breakpoint === 'mobile') { expandFirstRowOnMobile() } - if (breakpoint === 'desktop') { - // on desktop, the copy button is not visible until hovered - but cypress doesn't support that so use force - cy.get(`[data-testid^="copy-market-address"]`).first().click({ force: true }) - } else { - cy.get(`[data-testid^="copy-market-address"]:visible`).first().click() - } + // unfortunately we need to click twice on Chromium, the first one doesn't work (maybe due to the tooltip) + range(2).forEach(() => + breakpoint === 'desktop' + ? // on desktop, the copy button is not visible until hovered - but cypress doesn't support that so use force + cy.get(`[data-testid^="copy-market-address"]`).first().click({ force: true }) + : cy.get(`[data-testid^="copy-market-address"]:visible`).first().click(), + ) cy.get(`[data-testid="copy-confirmation"]`).should('be.visible') }) @@ -316,5 +317,5 @@ const selectCoin = (symbol: string, type: TokenType) => { cy.get(`[data-testid="multi-select-filter-${columnId}"]`).click() // open the menu again cy.get(`[data-testid="menu-${columnId}"] [value="${symbol}"]`).click() // select the token cy.get('body').click(0, 0) // close popover - cy.get(`[data-testid="data-table-cell-assets"] [data-testid^="token-icon-${symbol}"]`).should('be.visible') + cy.get(`[data-testid="data-table-cell-assets"] [data-testid^="token-icon-${symbol}"]`).should('exist') // token might be hidden behind other tokens } From 1565ec34e245c6bc293c50da42dcf1e5c839ddf1 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 30 May 2025 10:07:37 +0200 Subject: [PATCH 11/13] feat: expansion box shadow --- .../src/shared/ui/DataTable/ExpansionRow.tsx | 21 +++++++++++++------ .../src/themes/basic-theme/shadows.ts | 2 +- .../cypress/e2e/loan/llamalend-markets.cy.ts | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx index af394db2dd..c4cb83b463 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx @@ -6,7 +6,7 @@ import TableCell from '@mui/material/TableCell' import TableRow from '@mui/material/TableRow' import { type Row } from '@tanstack/react-table' import type { TableItem } from '@ui-kit/shared/ui/DataTable/data-table.utils' -import { getInsetShadow } from '@ui-kit/themes/basic-theme/shadows' +import { getInsetShadow, getShadow, type ShadowElevation } from '@ui-kit/themes/basic-theme/shadows' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' const { Spacing } = SizesAndSpaces @@ -26,12 +26,15 @@ export function ExpansionRow({ expandedPanel: ExpandedPanel colSpan: number }) { - const boxShadow = useInsetShadow() const { render, onExited, expanded } = useRowExpansion(row) + const { design } = useTheme() + const boxShadow = useMemo(() => getShadow(design, 3), [design]) + const inset = useMemo(() => getInsetShadow(design, 3), [design]) return ( render && ( - - + // add a scale(1) so the box-shadow is applied correctly on top of the next table row + + (row: Row) { * * The shadow is defined in the design, but it doesn't work between HTML rows. */ -function useInsetShadow() { +function useShadows(elevation: ShadowElevation) { const { design } = useTheme() - return useMemo(() => getInsetShadow(design, 3), [design]) + return useMemo( + () => [ + { boxShadow: getShadow(design, elevation), transform: 'scale(1)' }, + { boxShadow: getInsetShadow(design, elevation) }, + ], + [design], + ) } diff --git a/packages/curve-ui-kit/src/themes/basic-theme/shadows.ts b/packages/curve-ui-kit/src/themes/basic-theme/shadows.ts index 92819addd8..ec8fd59224 100644 --- a/packages/curve-ui-kit/src/themes/basic-theme/shadows.ts +++ b/packages/curve-ui-kit/src/themes/basic-theme/shadows.ts @@ -1,6 +1,6 @@ import type { DesignSystem } from '@ui-kit/themes/design' -type ShadowElevation = -2 | -1 | 1 | 2 | 3 +export type ShadowElevation = -2 | -1 | 1 | 2 | 3 export const getShadow = (design: DesignSystem, elevation: ShadowElevation) => ({ diff --git a/tests/cypress/e2e/loan/llamalend-markets.cy.ts b/tests/cypress/e2e/loan/llamalend-markets.cy.ts index 18b4593250..029ed474bb 100644 --- a/tests/cypress/e2e/loan/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/loan/llamalend-markets.cy.ts @@ -244,7 +244,9 @@ describe(`LlamaLend Markets`, () => { withFilterChips(() => { cy.get(`[data-testid="chip-rewards"]`).click() cy.get(`[data-testid^="data-table-row"]`).should('have.length', 1) - cy.get(`[data-testid="rewards-badge"]`).should('be.visible') + }) + cy.get(`[data-testid="rewards-badge"]`).should('be.visible') + withFilterChips(() => { cy.get(`[data-testid="chip-rewards"]`).click() cy.get(`[data-testid^="data-table-row"]`).should('have.length.above', 1) }) From 5178856ae05f4663339b8b8eae6a09a85fa5af37 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 30 May 2025 11:51:24 +0200 Subject: [PATCH 12/13] chore: self-review --- .../LlamaMarketExpandedPanel.tsx | 2 -- .../cells/MarketTitleCell/MarketTitleCell.tsx | 2 +- .../src/shared/ui/DataTable/ExpansionRow.tsx | 22 +++---------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx index 2e4e82c91d..b95b56094a 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LlamaMarketExpandedPanel.tsx @@ -80,9 +80,7 @@ export const LlamaMarketExpandedPanel: ExpandedPanel = ({ row: { or {t`7D Rate Chart`} - {/**/} - {/**/} diff --git a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx index c604210030..7776a55ce9 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/cells/MarketTitleCell/MarketTitleCell.tsx @@ -38,7 +38,7 @@ export const MarketTitleCell = ({ row: { original: market } }: CellContext diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx index c4cb83b463..527becabc4 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/ExpansionRow.tsx @@ -6,7 +6,7 @@ import TableCell from '@mui/material/TableCell' import TableRow from '@mui/material/TableRow' import { type Row } from '@tanstack/react-table' import type { TableItem } from '@ui-kit/shared/ui/DataTable/data-table.utils' -import { getInsetShadow, getShadow, type ShadowElevation } from '@ui-kit/themes/basic-theme/shadows' +import { getInsetShadow, getShadow } from '@ui-kit/themes/basic-theme/shadows' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' const { Spacing } = SizesAndSpaces @@ -29,12 +29,12 @@ export function ExpansionRow({ const { render, onExited, expanded } = useRowExpansion(row) const { design } = useTheme() const boxShadow = useMemo(() => getShadow(design, 3), [design]) - const inset = useMemo(() => getInsetShadow(design, 3), [design]) + const insetShadow = useMemo(() => getInsetShadow(design, 3), [design]) return ( render && ( // add a scale(1) so the box-shadow is applied correctly on top of the next table row - + (row: Row) { const onExited = useCallback(() => setRender(false), []) return { render, onExited, expanded } } - -/** - * Hook to get the inset shadow for the expansion row. - * - * The shadow is defined in the design, but it doesn't work between HTML rows. - */ -function useShadows(elevation: ShadowElevation) { - const { design } = useTheme() - return useMemo( - () => [ - { boxShadow: getShadow(design, elevation), transform: 'scale(1)' }, - { boxShadow: getInsetShadow(design, elevation) }, - ], - [design], - ) -} From 3be57b30186bb5414125a8e15aa6bd54792db50c Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 30 May 2025 12:03:32 +0200 Subject: [PATCH 13/13] refactor: hidden columns --- .../PageLlamaMarkets/LendingMarketsTable.tsx | 4 +- .../src/shared/ui/DataTable/DataCell.tsx | 9 +- .../src/shared/ui/DataTable/DataRow.tsx | 11 +-- .../src/shared/ui/DataTable/DataTable.tsx | 8 +- .../src/shared/ui/DataTable/HeaderCell.tsx | 84 +++++++++---------- .../TableVisibilitySettingsPopover.tsx | 15 +++- .../shared/ui/DataTable/data-table.utils.ts | 6 +- 7 files changed, 62 insertions(+), 75 deletions(-) diff --git a/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx b/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx index cdad3a76d2..0846e0fc84 100644 --- a/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx +++ b/apps/main/src/loan/components/PageLlamaMarkets/LendingMarketsTable.tsx @@ -29,7 +29,7 @@ import { TableFilters, useColumnFilters } from '@ui-kit/shared/ui/DataTable/Tabl import { useVisibilitySettings } from '@ui-kit/shared/ui/DataTable/TableVisibilitySettingsPopover' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' -const { Spacing, MaxWidth, Sizing } = SizesAndSpaces +const { Spacing, MaxWidth } = SizesAndSpaces /** * Hook to manage the visibility of columns in the Llama Markets table. @@ -39,7 +39,7 @@ const { Spacing, MaxWidth, Sizing } = SizesAndSpaces const useVisibility = (sorting: SortingState, hasPositions: boolean | undefined) => { const sortField = (sorting.length ? sorting : DEFAULT_SORT)[0].id as LlamaMarketColumnId const groups = useMemo(() => createLlamaMarketsColumnOptions(hasPositions), [hasPositions]) - const visibilitySettings = useVisibilitySettings(groups) + const visibilitySettings = useVisibilitySettings(groups, LLAMA_MARKET_COLUMNS) const columnVisibility = useMemo(() => createLlamaMarketsMobileColumns(sortField), [sortField]) return { sortField, ...visibilitySettings, ...(useIsMobile() && { columnVisibility }) } } diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx index ae58255259..5a10c9537d 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataCell.tsx @@ -17,15 +17,10 @@ export const DataCell = ({ cell, isMobile, isSticky, - isLast, - isFirst, }: { cell: Cell isMobile: boolean isSticky: boolean - // todo: get rid of column hidden meta and use visibility + use column.getIsLastColumn() - isFirst: boolean - isLast: boolean }) => { const { column, row } = cell const { variant, borderRight } = column.columnDef.meta ?? {} @@ -39,7 +34,7 @@ export const DataCell = ({ paddingBlock: mapValues({ ...Spacing.xs, mobile: Spacing.md.mobile }, (value) => `${value} calc(${value} - 1px)`), } - const showCollapseIcon = isMobile && isLast + const showCollapseIcon = isMobile && column.getIsLastColumn() return ( ({ component="td" sx={{ ...(!showCollapseIcon && wrapperSx), - ...getExtraColumnPadding({ isFirst, isLast }), + ...getExtraColumnPadding(column), ...((borderRight || isSticky) && { borderRight: (t) => `1px solid ${t.design.Layer[1].Outline}` }), ...(isSticky && { position: 'sticky', diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx index ee319d8f57..6dd62356e6 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataRow.tsx @@ -46,7 +46,7 @@ export const DataRow = ({ (e: MouseEvent) => onCellClick(e.target, url, push), [url, push], ) - const visibleCells = row.getVisibleCells().filter((cell) => !cell.column.columnDef.meta?.hidden) + const visibleCells = row.getVisibleCells() return ( <> @@ -80,14 +80,7 @@ export const DataRow = ({ onClick={isMobile ? () => row.toggleExpanded() : onClickDesktop} > {visibleCells.map((cell, index) => ( - + ))} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx index 88983c49e1..34322bf82e 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/DataTable.tsx @@ -54,13 +54,7 @@ export const DataTable = ({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header, index) => ( - + ))} ))} diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx index 901bbc6697..e6d6d6879c 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/HeaderCell.tsx @@ -10,59 +10,53 @@ const { Spacing } = SizesAndSpaces export const HeaderCell = ({ header, - isFirst, - isLast, isSticky, }: { header: Header - isFirst: boolean - isLast: boolean isSticky: boolean }) => { const { column } = header const isSorted = column.getIsSorted() const canSort = column.getCanSort() - const { hidden, borderRight } = column.columnDef.meta ?? {} + const { borderRight } = column.columnDef.meta ?? {} return ( - !hidden && ( - `1px solid ${t.design.Layer[1].Outline}` }), - ...(isSticky && { - position: 'sticky', - left: 0, - zIndex: (t) => t.zIndex.tableHeaderStickyColumn, - backgroundColor: (t) => t.design.Table.Header.Fill, - }), - }} - colSpan={header.colSpan} - onClick={column.getToggleSortingHandler()} - data-testid={`data-table-header-${column.id}`} - variant="tableHeaderS" - > - - {flexRender(column.columnDef.header, header.getContext())} - - - - ) + `1px solid ${t.design.Layer[1].Outline}` }), + ...(isSticky && { + position: 'sticky', + left: 0, + zIndex: (t) => t.zIndex.tableHeaderStickyColumn, + backgroundColor: (t) => t.design.Table.Header.Fill, + }), + }} + colSpan={header.colSpan} + onClick={column.getToggleSortingHandler()} + data-testid={`data-table-header-${column.id}`} + variant="tableHeaderS" + > + + {flexRender(column.columnDef.header, header.getContext())} + + + ) } diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx index 8a7d7acc36..a4388a588d 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableVisibilitySettingsPopover.tsx @@ -4,6 +4,7 @@ import Popover from '@mui/material/Popover' import Stack from '@mui/material/Stack' import Switch from '@mui/material/Switch' import Typography from '@mui/material/Typography' +import { ColumnDef } from '@tanstack/react-table' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' export type VisibilityOption = { @@ -102,7 +103,10 @@ const flatten = (visibilitySettings: VisibilityGroup(groups: VisibilityGroup[]) => { +export const useVisibilitySettings = ( + groups: VisibilityGroup[], + columns: ColumnDef[], +) => { /** current visibility settings in grouped format */ const [visibilitySettings, setVisibilitySettings] = useState(groups) @@ -126,7 +130,14 @@ export const useVisibilitySettings = (groups: Visibili ) /** current column visibility state as used internally by tanstack */ - const columnVisibility = useMemo(() => flatten(visibilitySettings), [visibilitySettings]) + const columnVisibility = useMemo( + () => + ({ + ...flatten(visibilitySettings), + ...Object.fromEntries(columns.filter((c) => c.meta?.hidden).map((c) => [c.id, false])), + }) as Record, + [columns, visibilitySettings], + ) return { columnSettings: visibilitySettings, columnVisibility, toggleVisibility } } diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts b/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts index 26f2e2d8de..6f727e7023 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/data-table.utils.ts @@ -26,9 +26,9 @@ const { Spacing } = SizesAndSpaces * In the figma design, the first and last columns seem to be aligned to the table title. * However, the normal padding causes them to be misaligned. */ -export const getExtraColumnPadding = ({ isFirst, isLast }: { isFirst: boolean; isLast: boolean }) => ({ - ...(isFirst && { paddingInlineStart: Spacing.md }), - ...(isLast && { paddingInlineEnd: Spacing.md }), +export const getExtraColumnPadding = (column: Column) => ({ + ...(column.getIsFirstColumn() && { paddingInlineStart: Spacing.md }), + ...(column.getIsLastColumn() && { paddingInlineEnd: Spacing.md }), }) export type FilterProps = {