From c3e39b6e15ece7ccbcc1acf51aac0b3d557616dd Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Mon, 26 Jun 2023 17:10:22 -0700 Subject: [PATCH] refactor(trading): pane stores for grid state (#4119) --- .../src/integration/trading-positions.cy.ts | 73 ++++--- .../client-pages/liquidity/liquidity.tsx | 67 +------ .../client-pages/market/trade-grid.tsx | 57 +----- .../client-pages/market/trade-views.tsx | 33 ++-- apps/trading/client-pages/markets/closed.tsx | 1 - .../portfolio/deposits-container.tsx | 15 +- .../client-pages/portfolio/portfolio.tsx | 53 ++--- .../accounts-container/accounts-container.tsx | 21 +- .../fills-container/fills-container.tsx | 49 +++++ .../components/fills-container/index.ts | 1 + .../components/ledger-container/index.ts | 1 + .../ledger-container/ledger-container.tsx | 36 ++++ .../components/liquidity-container/index.ts | 1 + .../liquidity-container.tsx | 93 +++++++++ .../components/orders-container/index.ts | 1 + .../orders-container.spec.tsx | 106 ++++++++++ .../orders-container/orders-container.tsx | 166 ++++++++++++++++ .../components/positions-container/index.ts | 1 + .../positions-container.tsx | 26 ++- apps/trading/stores/datagrid-store-slice.ts | 25 +++ .../src/lib/accounts-manager.spec.tsx | 13 ++ libs/accounts/src/lib/accounts-manager.tsx | 8 +- libs/accounts/src/lib/accounts-table.tsx | 2 - libs/datagrid/src/index.ts | 3 +- .../src/lib/ag-grid/ag-grid-lazy-themed.tsx | 14 +- .../datagrid/src/lib/ag-grid/ag-grid-lazy.tsx | 1 - .../src/lib/ag-grid/use-column-sizes.spec.ts | 122 ------------ .../src/lib/ag-grid/use-column-sizes.ts | 145 -------------- .../src/lib/use-bottom-placeholder.tsx | 68 ------- .../src/lib/use-datagrid-events.spec.tsx | 185 ++++++++++++++++++ libs/datagrid/src/lib/use-datagrid-events.ts | 66 +++++++ libs/deposits/src/lib/deposits-table.tsx | 4 +- libs/fills/src/index.ts | 2 +- libs/fills/src/lib/fills-container.tsx | 33 ---- libs/fills/src/lib/fills-manager.tsx | 12 +- libs/fills/src/lib/fills-table.tsx | 1 - libs/ledger/src/index.ts | 1 - libs/ledger/src/lib/ledger-container.tsx | 17 -- libs/ledger/src/lib/ledger-manager.tsx | 26 ++- libs/ledger/src/lib/ledger-table.tsx | 5 +- libs/liquidity/src/lib/liquidity-table.tsx | 1 - .../markets-container/market-list-table.tsx | 1 - libs/orders/src/lib/components/index.ts | 1 - .../lib/components/order-list-container.tsx | 42 ---- .../order-list-manager/order-list-manager.tsx | 90 ++------- .../lib/components/order-list/order-list.tsx | 1 - libs/positions/src/index.ts | 2 +- libs/positions/src/lib/positions-manager.tsx | 9 +- libs/positions/src/lib/positions-table.tsx | 1 - .../proposals-list/proposals-list.tsx | 1 - libs/trades/src/lib/trades-table.tsx | 1 - libs/withdraws/src/lib/withdrawals-table.tsx | 6 +- 52 files changed, 928 insertions(+), 782 deletions(-) create mode 100644 apps/trading/components/fills-container/fills-container.tsx create mode 100644 apps/trading/components/fills-container/index.ts create mode 100644 apps/trading/components/ledger-container/index.ts create mode 100644 apps/trading/components/ledger-container/ledger-container.tsx create mode 100644 apps/trading/components/liquidity-container/index.ts create mode 100644 apps/trading/components/liquidity-container/liquidity-container.tsx create mode 100644 apps/trading/components/orders-container/index.ts create mode 100644 apps/trading/components/orders-container/orders-container.spec.tsx create mode 100644 apps/trading/components/orders-container/orders-container.tsx create mode 100644 apps/trading/components/positions-container/index.ts rename {libs/positions/src/lib => apps/trading/components/positions-container}/positions-container.tsx (51%) create mode 100644 apps/trading/stores/datagrid-store-slice.ts delete mode 100644 libs/datagrid/src/lib/ag-grid/use-column-sizes.spec.ts delete mode 100644 libs/datagrid/src/lib/ag-grid/use-column-sizes.ts delete mode 100644 libs/datagrid/src/lib/use-bottom-placeholder.tsx create mode 100644 libs/datagrid/src/lib/use-datagrid-events.spec.tsx create mode 100644 libs/datagrid/src/lib/use-datagrid-events.ts delete mode 100644 libs/fills/src/lib/fills-container.tsx delete mode 100644 libs/ledger/src/lib/ledger-container.tsx delete mode 100644 libs/orders/src/lib/components/order-list-container.tsx diff --git a/apps/trading-e2e/src/integration/trading-positions.cy.ts b/apps/trading-e2e/src/integration/trading-positions.cy.ts index 74c637139..9159063d6 100644 --- a/apps/trading-e2e/src/integration/trading-positions.cy.ts +++ b/apps/trading-e2e/src/integration/trading-positions.cy.ts @@ -1,5 +1,4 @@ -import { checkSorting } from '@vegaprotocol/cypress'; -import { aliasGQLQuery } from '@vegaprotocol/cypress'; +import { checkSorting, aliasGQLQuery } from '@vegaprotocol/cypress'; import { marketsDataQuery } from '@vegaprotocol/mock'; import { positionsQuery } from '@vegaprotocol/mock'; @@ -15,13 +14,12 @@ const toastContent = 'toast-content'; const tooltipContent = 'tooltip-content'; // #endregion -beforeEach(() => { - cy.mockTradingPage(); - cy.mockSubscription(); - cy.setVegaWallet(); -}); - describe('positions', { tags: '@smoke', testIsolation: true }, () => { + beforeEach(() => { + cy.mockTradingPage(); + cy.mockSubscription(); + cy.setVegaWallet(); + }); it('renders positions on trading page', () => { visitAndClickPositions(); // 7004-POSI-001 @@ -64,7 +62,14 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => { ); }); }); + describe('positions', { tags: '@regression', testIsolation: true }, () => { + beforeEach(() => { + cy.mockTradingPage(); + cy.mockSubscription(); + cy.setVegaWallet(); + }); + it('rows should be displayed despite errors', () => { const errors = [ { @@ -164,8 +169,9 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => { ); }); + // let elementWidth: number; + it('Resize column', () => { - let elementWidth: number; visitAndClickPositions(); cy.get('.ag-overlay-loading-wrapper').should('not.be.visible'); cy.get('.ag-header-container').within(() => { @@ -180,29 +186,33 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => { cy.get(`[col-id="marketName"]`) .invoke('width') .should('be.greaterThan', 250); - cy.get(`[col-id="marketName"]`) - .invoke('width') - .then((width) => { - elementWidth = width as number; - }) - .then(() => { - let localStorageCopy: Record; - cy.window().then((win) => { - localStorageCopy = { ...win.localStorage }; - }); + }); - cy.reload(); - cy.window().then((win) => { - Object.keys(localStorageCopy).forEach((key) => { - win.localStorage.setItem(key, localStorageCopy[key]); - }); - }); + // This test depends on the previous one + it('Has persisted column widths', () => { + const width = 400; - // 7004-POSI-012 - cy.get('[col-id="marketName"]') - .invoke('width') - .should('equal', elementWidth); - }); + cy.window().then((win) => { + win.localStorage.setItem( + 'vega_positions_store', + JSON.stringify({ + state: { + gridStore: { + columnState: [{ colId: 'marketName', width }], + }, + }, + }) + ); + }); + + visitAndClickPositions(); + + // 7004-POSI-012 + cy.get('.ag-center-cols-container .ag-row') + .first() + .find('[col-id="marketName"]') + .invoke('outerWidth') + .should('equal', width); }); it('Scroll horizontally', () => { @@ -291,6 +301,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => { cy.getByTestId(dialogContent).should('be.visible'); }); }); + function validatePositionsDisplayed(multiKey = false) { cy.getByTestId('tab-positions').should('be.visible'); cy.getByTestId('tab-positions') @@ -326,6 +337,7 @@ function validatePositionsDisplayed(multiKey = false) { cy.getByTestId('close-position').should('be.visible').and('have.length', 3); } + function assertPNLColor( pnlSelector: string, positiveClass: string, @@ -347,6 +359,7 @@ function assertPNLColor( } }); } + function visitAndClickPositions() { cy.visit('/#/markets/market-0'); cy.getByTestId(positions).click(); diff --git a/apps/trading/client-pages/liquidity/liquidity.tsx b/apps/trading/client-pages/liquidity/liquidity.tsx index a15dded2f..a67aaa78c 100644 --- a/apps/trading/client-pages/liquidity/liquidity.tsx +++ b/apps/trading/client-pages/liquidity/liquidity.tsx @@ -1,7 +1,5 @@ import { matchFilter, - liquidityProvisionsDataProvider, - LiquidityTable, lpAggregatedDataProvider, useCheckLiquidityStatus, } from '@vegaprotocol/liquidity'; @@ -24,18 +22,16 @@ import { ExternalLink, } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { memo, useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import { Header, HeaderStat, HeaderTitle } from '../../components/header'; -import type { AgGridReact } from 'ag-grid-react'; - -import type { Filter } from '@vegaprotocol/liquidity'; import { Link, useParams } from 'react-router-dom'; import { Links, Routes } from '../../pages/client-router'; import { useMarket, useStaticMarketData } from '@vegaprotocol/markets'; import { DocsLinks } from '@vegaprotocol/environment'; +import { LiquidityContainer } from '../../components/liquidity-container'; const enum LiquidityTabs { Active = 'active', @@ -49,65 +45,6 @@ export const Liquidity = () => { return ; }; -const useReloadLiquidityData = (marketId: string | undefined) => { - const { reload } = useDataProvider({ - dataProvider: liquidityProvisionsDataProvider, - variables: { marketId: marketId || '' }, - update: () => true, - skip: !marketId, - }); - useEffect(() => { - const interval = setInterval(reload, 30000); - return () => clearInterval(interval); - }, [reload]); -}; - -export const LiquidityContainer = ({ - marketId, - filter, -}: { - marketId: string | undefined; - filter?: Filter; -}) => { - const gridRef = useRef(null); - const { data: market } = useMarket(marketId); - - // To be removed when liquidityProvision subscriptions are working - useReloadLiquidityData(marketId); - - const { data, error } = useDataProvider({ - dataProvider: lpAggregatedDataProvider, - variables: { marketId: marketId || '', filter }, - skip: !marketId, - }); - - const assetDecimalPlaces = - market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0; - const quantum = - market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0; - const symbol = - market?.tradableInstrument.instrument.product.settlementAsset.symbol; - - const { params } = useNetworkParams([ - NetworkParams.market_liquidity_stakeToCcyVolume, - ]); - const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume; - - return ( -
- -
- ); -}; - const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => { const { data: market } = useMarket(marketId); const { data: marketData } = useStaticMarketData(marketId); diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index 3d0bb96cb..dcf890c18 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -19,10 +19,7 @@ import { VegaIcon, VegaIconNames, } from '@vegaprotocol/ui-toolkit'; -import { - useMarketClickHandler, - useMarketLiquidityClickHandler, -} from '../../lib/hooks/use-market-click-handler'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; import { VegaWalletContainer } from '../../components/vega-wallet-container'; import { HeaderTitle } from '../../components/header'; import { @@ -49,7 +46,6 @@ const MarketBottomPanel = memo( const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' }); const { screenSize } = useScreenDimensions(); const onMarketClick = useMarketClickHandler(true); - const onOrderTypeClick = useMarketLiquidityClickHandler(); return 'xxxl' === screenSize ? ( @@ -81,10 +73,6 @@ const MarketBottomPanel = memo( @@ -93,22 +81,12 @@ const MarketBottomPanel = memo( - + @@ -116,7 +94,6 @@ const MarketBottomPanel = memo( @@ -134,8 +111,6 @@ const MarketBottomPanel = memo( @@ -145,7 +120,6 @@ const MarketBottomPanel = memo( pinnedAsset={pinnedAsset} onMarketClick={onMarketClick} hideButtons - storeKey="marketCollateral" /> @@ -158,10 +132,7 @@ const MarketBottomPanel = memo( - + @@ -169,10 +140,6 @@ const MarketBottomPanel = memo( @@ -181,10 +148,6 @@ const MarketBottomPanel = memo( @@ -193,22 +156,12 @@ const MarketBottomPanel = memo( - + @@ -216,7 +169,6 @@ const MarketBottomPanel = memo( @@ -226,7 +178,6 @@ const MarketBottomPanel = memo( pinnedAsset={pinnedAsset} onMarketClick={onMarketClick} hideButtons - storeKey="marketCollateral" /> diff --git a/apps/trading/client-pages/market/trade-views.tsx b/apps/trading/client-pages/market/trade-views.tsx index a0e284a7e..4d9405e38 100644 --- a/apps/trading/client-pages/market/trade-views.tsx +++ b/apps/trading/client-pages/market/trade-views.tsx @@ -1,18 +1,19 @@ +import type { ComponentProps } from 'react'; +import { Splash } from '@vegaprotocol/ui-toolkit'; import { DealTicketContainer } from '@vegaprotocol/deal-ticket'; import { MarketInfoAccordionContainer } from '@vegaprotocol/markets'; -import { OrderbookContainer } from '@vegaprotocol/market-depth'; -import { OrderListContainer, Filter } from '@vegaprotocol/orders'; -import type { OrderListContainerProps } from '@vegaprotocol/orders'; -import { FillsContainer } from '@vegaprotocol/fills'; -import { PositionsContainer } from '@vegaprotocol/positions'; import { TradesContainer } from '@vegaprotocol/trades'; -import type { ComponentProps } from 'react'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; -import { Splash } from '@vegaprotocol/ui-toolkit'; -import { AccountsContainer } from '../../components/accounts-container'; +import { OrderbookContainer } from '@vegaprotocol/market-depth'; +import { Filter } from '@vegaprotocol/orders'; import { NO_MARKET } from './constants'; -import { LiquidityContainer } from '../liquidity/liquidity'; +import { FillsContainer } from '../../components/fills-container'; +import { PositionsContainer } from '../../components/positions-container'; +import { AccountsContainer } from '../../components/accounts-container'; +import { LiquidityContainer } from '../../components/liquidity-container'; +import type { OrderContainerProps } from '../../components/orders-container'; +import { OrdersContainer } from '../../components/orders-container'; type MarketDependantView = | typeof CandlesChartContainer @@ -65,25 +66,25 @@ export const TradingViews = { positions: { label: 'Positions', component: PositionsContainer }, activeOrders: { label: 'Active', - component: (props: OrderListContainerProps) => ( - + component: (props: OrderContainerProps) => ( + ), }, closedOrders: { label: 'Closed', - component: (props: OrderListContainerProps) => ( - + component: (props: OrderContainerProps) => ( + ), }, rejectedOrders: { label: 'Rejected', - component: (props: OrderListContainerProps) => ( - + component: (props: OrderContainerProps) => ( + ), }, orders: { label: 'All', - component: OrderListContainer, + component: OrdersContainer, }, collateral: { label: 'Collateral', component: AccountsContainer }, fills: { label: 'Fills', component: FillsContainer }, diff --git a/apps/trading/client-pages/markets/closed.tsx b/apps/trading/client-pages/markets/closed.tsx index 26094faa8..4908135c9 100644 --- a/apps/trading/client-pages/markets/closed.tsx +++ b/apps/trading/client-pages/markets/closed.tsx @@ -313,7 +313,6 @@ const ClosedMarketsDataGrid = ({ minWidth: 100, }} overlayNoRowsTemplate={error ? error.message : t('No markets')} - storeKey="closedMarkets" /> ); }; diff --git a/apps/trading/client-pages/portfolio/deposits-container.tsx b/apps/trading/client-pages/portfolio/deposits-container.tsx index 769540502..9046f5e7f 100644 --- a/apps/trading/client-pages/portfolio/deposits-container.tsx +++ b/apps/trading/client-pages/portfolio/deposits-container.tsx @@ -2,7 +2,6 @@ import { Button } from '@vegaprotocol/ui-toolkit'; import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits'; import { depositsProvider } from '@vegaprotocol/deposits'; import { t } from '@vegaprotocol/i18n'; -import { useBottomPlaceholder } from '@vegaprotocol/datagrid'; import { useDataProvider } from '@vegaprotocol/data-provider'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useRef } from 'react'; @@ -17,17 +16,13 @@ export const DepositsContainer = () => { skip: !pubKey, }); const openDepositDialog = useDepositDialog((state) => state.open); - const bottomPlaceholderProps = useBottomPlaceholder({ gridRef }); return (
-
- -
+ {!isReadOnly && (
-
-); - export const OrderListManager = ({ partyId, marketId, onMarketClick, onOrderTypeClick, isReadOnly, - enforceBottomPlaceholder, filter, - storeKey, + gridProps, }: OrderListManagerProps) => { const gridRef = useRef(null); const [editOrder, setEditOrder] = useState(null); @@ -91,14 +61,6 @@ export const OrderListManager = ({ }, }); - const { - onFilterChanged: bottomPlaceholderOnFilterChanged, - ...bottomPlaceholderProps - } = useBottomPlaceholder({ - gridRef, - disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder, - }); - const cancel = useCallback( (order: Order) => { if (!order.market) return; @@ -112,26 +74,6 @@ export const OrderListManager = ({ [create] ); - const onGridReady = useCallback( - ({ api }: GridReadyEvent) => { - if (filter !== undefined) { - api.setFilterModel({ - status: { - value: FilterStatusValue[filter], - }, - }); - } - }, - [filter] - ); - - const onFilterChanged = useCallback( - (event: FilterChangedEvent) => { - bottomPlaceholderOnFilterChanged?.(); - }, - [bottomPlaceholderOnFilterChanged] - ); - const cancelAll = useCallback(() => { create({ orderCancellation: {}, @@ -142,20 +84,17 @@ export const OrderListManager = ({ <>
{!isReadOnly && hasAmendableOrder && ( @@ -198,3 +137,16 @@ export const OrderListManager = ({ ); }; + +const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => ( +
+ +
+); diff --git a/libs/orders/src/lib/components/order-list/order-list.tsx b/libs/orders/src/lib/components/order-list/order-list.tsx index ed4bbc6ef..b5a1aba40 100644 --- a/libs/orders/src/lib/components/order-list/order-list.tsx +++ b/libs/orders/src/lib/components/order-list/order-list.tsx @@ -39,7 +39,6 @@ export type OrderListTableProps = TypedDataAgGrid & { onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void; filter?: Filter; isReadOnly: boolean; - storeKey?: string; }; export const OrderListTable = memo< diff --git a/libs/positions/src/index.ts b/libs/positions/src/index.ts index c48ad2b54..98f451cd5 100644 --- a/libs/positions/src/index.ts +++ b/libs/positions/src/index.ts @@ -1,6 +1,6 @@ export * from './lib/__generated__/Positions'; -export * from './lib/positions-container'; export * from './lib/positions-data-providers'; export * from './lib/positions-table'; +export * from './lib/positions-manager'; export * from './lib/use-market-margin'; export * from './lib/use-open-volume'; diff --git a/libs/positions/src/lib/positions-manager.tsx b/libs/positions/src/lib/positions-manager.tsx index 517ee62f0..c0e5c5336 100644 --- a/libs/positions/src/lib/positions-manager.tsx +++ b/libs/positions/src/lib/positions-manager.tsx @@ -8,22 +8,21 @@ import { positionsMetricsProvider, positionsMarketsProvider, } from './positions-data-providers'; +import type { useDataGridEvents } from '@vegaprotocol/datagrid'; import { useVegaWallet } from '@vegaprotocol/wallet'; interface PositionsManagerProps { partyIds: string[]; onMarketClick?: (marketId: string) => void; isReadOnly: boolean; - noBottomPlaceholder?: boolean; - storeKey?: string; + gridProps: ReturnType; } export const PositionsManager = ({ partyIds, onMarketClick, isReadOnly, - noBottomPlaceholder, - storeKey, + gridProps, }: PositionsManagerProps) => { const { pubKeys, pubKey } = useVegaWallet(); const create = useVegaTransactionStore((store) => store.create); @@ -74,9 +73,9 @@ export const PositionsManager = ({ onMarketClick={onMarketClick} onClose={onClose} isReadOnly={isReadOnly} - storeKey={storeKey} multipleKeys={partyIds.length > 1} overlayNoRowsTemplate={error ? error.message : t('No positions')} + {...gridProps} />
); diff --git a/libs/positions/src/lib/positions-table.tsx b/libs/positions/src/lib/positions-table.tsx index a774b6cfa..08a301b68 100644 --- a/libs/positions/src/lib/positions-table.tsx +++ b/libs/positions/src/lib/positions-table.tsx @@ -48,7 +48,6 @@ interface Props extends TypedDataAgGrid { onMarketClick?: (id: string, metaKey?: boolean) => void; style?: CSSProperties; isReadOnly: boolean; - storeKey?: string; multipleKeys?: boolean; pubKeys?: VegaWalletContextShape['pubKeys']; pubKey?: VegaWalletContextShape['pubKey']; diff --git a/libs/proposals/src/components/proposals-list/proposals-list.tsx b/libs/proposals/src/components/proposals-list/proposals-list.tsx index 392d5974a..d874bd8a5 100644 --- a/libs/proposals/src/components/proposals-list/proposals-list.tsx +++ b/libs/proposals/src/components/proposals-list/proposals-list.tsx @@ -38,7 +38,6 @@ export const ProposalsList = () => { columnDefs={columnDefs} rowData={filteredData} defaultColDef={defaultColDef} - storeKey="proposedMarkets" getRowId={({ data }) => data.id} style={{ width: '100%', height: '100%' }} overlayNoRowsTemplate={error ? error.message : t('No markets')} diff --git a/libs/trades/src/lib/trades-table.tsx b/libs/trades/src/lib/trades-table.tsx index 1482dc9b1..09729b534 100644 --- a/libs/trades/src/lib/trades-table.tsx +++ b/libs/trades/src/lib/trades-table.tsx @@ -55,7 +55,6 @@ export const TradesTable = forwardRef((props, ref) => { ref={ref} defaultColDef={{ flex: 1, - resizable: true, }} {...props} > diff --git a/libs/withdraws/src/lib/withdrawals-table.tsx b/libs/withdraws/src/lib/withdrawals-table.tsx index 4c1336b6b..7996abd7d 100644 --- a/libs/withdraws/src/lib/withdrawals-table.tsx +++ b/libs/withdraws/src/lib/withdrawals-table.tsx @@ -8,7 +8,6 @@ import { isNumeric, truncateByChars, } from '@vegaprotocol/utils'; -import { useBottomPlaceholder } from '@vegaprotocol/datagrid'; import { t } from '@vegaprotocol/i18n'; import { ButtonLink, @@ -47,11 +46,10 @@ export const WithdrawalsTable = ( (store) => store.create ); - const bottomPlaceholderProps = useBottomPlaceholder({ gridRef }); return (