chore(trading): split bottom panel into two parts (#3205)

This commit is contained in:
Maciek 2023-03-17 10:50:43 +01:00 committed by GitHub
parent 300019f108
commit 84795b2d6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 323 additions and 187 deletions

View File

@ -10,7 +10,7 @@ export const Footer = () => {
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false); const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo( const showFullFeedbackLabel = useMemo(
() => ['lg', 'xl'].includes(screenSize), () => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
[screenSize] [screenSize]
); );

View File

@ -216,6 +216,60 @@ describe('Market trading page', () => {
}); });
}); });
}); });
describe('market bottom panel', { tags: '@smoke' }, () => {
it('on xxl screen should be splitted out into two tables', () => {
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.viewport(1801, 1000);
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('Fills').click();
cy.getByTestId('Collateral').click();
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'active'
);
});
});
}); });
describe('market states not accepting orders', { tags: '@smoke' }, function () { describe('market states not accepting orders', { tags: '@smoke' }, function () {

View File

@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import debounce from 'lodash/debounce';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
useDataProvider, useDataProvider,
useScreenDimensions,
useThrottledDataProvider, useThrottledDataProvider,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
@ -61,7 +61,8 @@ export const MarketPage = () => {
const { marketId } = useParams(); const { marketId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { w } = useWindowSize(); const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update); const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId); const lastMarketId = useGlobalStore((store) => store.marketId);
@ -87,7 +88,7 @@ export const MarketPage = () => {
}, [update, lastMarketId, data?.id]); }, [update, lastMarketId, data?.id]);
const tradeView = useMemo(() => { const tradeView = useMemo(() => {
if (w > 960) { if (largeScreen) {
return ( return (
<TradeGrid <TradeGrid
market={data} market={data}
@ -105,7 +106,7 @@ export const MarketPage = () => {
onClickCollateral={() => navigate('/portfolio')} onClickCollateral={() => navigate('/portfolio')}
/> />
); );
}, [w, data, onSelect, navigate]); }, [largeScreen, data, onSelect, navigate]);
if (!data && marketId) { if (!data && marketId) {
return ( return (
<Splash> <Splash>
@ -140,37 +141,3 @@ export const MarketPage = () => {
</AsyncRenderer> </AsyncRenderer>
); );
}; };
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState(() => {
if (typeof window !== 'undefined') {
return {
w: window.innerWidth,
h: window.innerHeight,
};
}
// Something sensible for server rendered page
return {
w: 1200,
h: 900,
};
});
useEffect(() => {
const handleResize = debounce(({ target }) => {
setWindowSize({
w: target.innerWidth,
h: target.innerHeight,
});
}, 300);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
};

View File

@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades';
import { LayoutPriority } from 'allotment'; import { LayoutPriority } from 'allotment';
import classNames from 'classnames'; import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { memo, useState } from 'react'; import { memo, useCallback, useState } from 'react';
import type { ReactNode, ComponentProps } from 'react'; import type { ReactNode, ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
@ -29,6 +29,7 @@ import { LiquidityContainer } from '../liquidity/liquidity';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router'; import { Links, Routes } from '../../pages/client-router';
import type { PinnedAsset } from '@vegaprotocol/accounts'; import type { PinnedAsset } from '@vegaprotocol/accounts';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
type MarketDependantView = type MarketDependantView =
| typeof CandlesChartContainer | typeof CandlesChartContainer
@ -69,21 +70,127 @@ interface TradeGridProps {
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
} }
const MainGrid = ({ interface BottomPanelProps {
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string; marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
}) => { }
const MarketBottomPanel = memo(
({ marketId, pinnedAsset }: BottomPanelProps) => {
const { screenSize } = useScreenDimensions();
const navigate = useNavigate(); const navigate = useNavigate();
const onMarketClick = (marketId: string) => { const onMarketClick = useCallback(
(marketId: string) => {
navigate(Links[Routes.MARKET](marketId), { navigate(Links[Routes.MARKET](marketId), {
replace: true, replace: true,
}); });
}; },
[navigate]
);
return 'xxxl' === screenSize ? (
<ResizableGrid proportionalLayout minSize={200}>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="50%"
minSize={50}
>
<TradeGridChild>
<Tabs>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="50%"
minSize={50}
>
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions
onMarketClick={onMarketClick}
noBottomPlaceholder
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
noBottomPlaceholder
hideButtons
/>
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
) : (
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
);
}
);
MarketBottomPanel.displayName = 'MarketBottomPanel';
const MainGrid = memo(
({
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
return ( return (
<ResizableGrid vertical> <ResizableGrid vertical>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}> <ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
@ -154,44 +261,13 @@ const MainGrid = ({
preferredSize="25%" preferredSize="25%"
minSize={50} minSize={50}
> >
<TradeGridChild> <MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
hideButtons
/>
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel> </ResizableGridPanel>
</ResizableGrid> </ResizableGrid>
); );
}; }
const MainGridWrapped = memo(MainGrid); );
MainGrid.displayName = 'MainGrid';
export const TradeGrid = ({ export const TradeGrid = ({
market, market,
@ -201,7 +277,7 @@ export const TradeGrid = ({
return ( return (
<div className="h-full grid grid-rows-[min-content_1fr]"> <div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} /> <TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped <MainGrid
marketId={market?.id || ''} marketId={market?.id || ''}
onSelect={onSelect} onSelect={onSelect}
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}

View File

@ -12,9 +12,11 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = ({ export const AccountsContainer = ({
pinnedAsset, pinnedAsset,
hideButtons, hideButtons,
noBottomPlaceholder,
}: { }: {
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
hideButtons?: boolean; hideButtons?: boolean;
noBottomPlaceholder?: boolean;
}) => { }) => {
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@ -46,6 +48,7 @@ export const AccountsContainer = ({
onClickDeposit={openDepositDialog} onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder}
/> />
{!isReadOnly && !hideButtons && ( {!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded"> <div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">

View File

@ -19,6 +19,7 @@ interface AccountManagerProps {
onClickDeposit?: (assetId?: string) => void; onClickDeposit?: (assetId?: string) => void;
isReadOnly: boolean; isReadOnly: boolean;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean;
} }
export const AccountManager = ({ export const AccountManager = ({
@ -28,6 +29,7 @@ export const AccountManager = ({
partyId, partyId,
isReadOnly, isReadOnly,
pinnedAsset, pinnedAsset,
noBottomPlaceholder,
}: AccountManagerProps) => { }: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId }), [partyId]);
@ -45,6 +47,7 @@ export const AccountManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({ const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
gridRef, gridRef,
setId, setId,
disabled: noBottomPlaceholder,
}); });
const getRowHeight = useCallback( const getRowHeight = useCallback(

View File

@ -68,10 +68,11 @@ export const PositionsManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<Position>({ const bottomPlaceholderProps = useBottomPlaceholder<Position>({
gridRef, gridRef,
setId, setId,
disabled: noBottomPlaceholder,
}); });
useEffect(() => { useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data]); }, [data?.length]);
const onFilterChanged = useCallback((event: FilterChangedEvent) => { const onFilterChanged = useCallback((event: FilterChangedEvent) => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []); }, []);
@ -86,7 +87,7 @@ export const PositionsManager = ({
suppressNoRowsOverlay suppressNoRowsOverlay
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
onFilterChanged={onFilterChanged} onFilterChanged={onFilterChanged}
{...(noBottomPlaceholder ? null : bottomPlaceholderProps)} {...bottomPlaceholderProps}
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer

View File

@ -116,6 +116,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
value value
) )
} }
minWidth={190}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Notional')} headerName={t('Notional')}
@ -141,6 +142,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={80}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Open volume')} headerName={t('Open volume')}
@ -174,6 +176,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
); );
}} }}
cellRenderer={OpenVolumeCell} cellRenderer={OpenVolumeCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Mark price')} headerName={t('Mark price')}
@ -213,8 +216,13 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={100}
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="assetSymbol"
minWidth={100}
/> />
<AgGridColumn headerName={t('Settlement asset')} field="assetSymbol" />
<AgGridColumn <AgGridColumn
headerName={t('Entry price')} headerName={t('Entry price')}
field="averageEntryPrice" field="averageEntryPrice"
@ -248,6 +256,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces data.marketDecimalPlaces
); );
}} }}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Leverage')} headerName={t('Leverage')}
@ -264,6 +273,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
}: VegaValueFormatterParams<Position, 'currentLeverage'>) => }: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
value === undefined ? undefined : formatNumber(value.toString(), 1) value === undefined ? undefined : formatNumber(value.toString(), 1)
} }
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Margin allocated')} headerName={t('Margin allocated')}
@ -295,6 +305,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.decimals data.decimals
); );
}} }}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Realised PNL')} headerName={t('Realised PNL')}
@ -321,6 +332,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.' 'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
)} )}
cellRenderer={PNLCell} cellRenderer={PNLCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Unrealised PNL')} headerName={t('Unrealised PNL')}
@ -347,6 +359,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.' 'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
)} )}
cellRenderer={PNLCell} cellRenderer={PNLCell}
minWidth={100}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Updated')} headerName={t('Updated')}
@ -361,6 +374,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
} }
return getDateTimeFormat().format(new Date(value)); return getDateTimeFormat().format(new Date(value));
}} }}
minWidth={150}
/> />
{onClose && !props.isReadOnly ? ( {onClose && !props.isReadOnly ? (
<AgGridColumn <AgGridColumn
@ -375,6 +389,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
</ButtonLink> </ButtonLink>
) : null ) : null
} }
minWidth={80}
/> />
) : null} ) : null}
</AgGrid> </AgGrid>

View File

@ -11,11 +11,13 @@ const isFullWidthRow = (params: IsFullWidthRowParams) =>
interface Props<T> { interface Props<T> {
gridRef: RefObject<AgGridReact>; gridRef: RefObject<AgGridReact>;
setId?: (data: T) => T; setId?: (data: T) => T;
disabled?: boolean;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
export const useBottomPlaceholder = <T extends {}>({ export const useBottomPlaceholder = <T extends {}>({
gridRef, gridRef,
setId, setId,
disabled,
}: Props<T>) => { }: Props<T>) => {
const onBodyScrollEnd = useCallback(() => { const onBodyScrollEnd = useCallback(() => {
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0; const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
@ -58,14 +60,17 @@ export const useBottomPlaceholder = <T extends {}>({
}, [gridRef, onBodyScrollEnd]); }, [gridRef, onBodyScrollEnd]);
return useMemo( return useMemo(
() => ({ () =>
!disabled
? {
onBodyScrollEnd, onBodyScrollEnd,
rowClassRules: NO_HOVER_CSS_RULE, rowClassRules: NO_HOVER_CSS_RULE,
isFullWidthRow, isFullWidthRow,
fullWidthCellRenderer, fullWidthCellRenderer,
onSortChanged: onRowsChanged, onSortChanged: onRowsChanged,
onFilterChange: onRowsChanged, onFilterChange: onRowsChanged,
}), }
[onBodyScrollEnd, onRowsChanged] : {},
[onBodyScrollEnd, onRowsChanged, disabled]
); );
}; };

View File

@ -1,10 +1,19 @@
import { useRef, useEffect, useState } from 'react'; import { useRef, useEffect, useState } from 'react';
const SERVER_SIDE_DIMENSIONS = {
width: 1200,
height: 900,
};
export const useResize = () => { export const useResize = () => {
const [windowSize, setWindowSize] = useState({ const [windowSize, setWindowSize] = useState(
typeof window !== undefined
? {
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,
}); }
: { ...SERVER_SIDE_DIMENSIONS }
);
const timeout = useRef(0); const timeout = useRef(0);

View File

@ -1,29 +1,31 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
// @ts-ignore avoid adding declaration file
import { theme } from '@vegaprotocol/tailwindcss-config'; import { theme } from '@vegaprotocol/tailwindcss-config';
import { useResize } from './use-resize'; import { useResize } from './use-resize';
type Screen = keyof typeof theme.screens; export type Screen = keyof typeof theme.screens;
interface Props { interface Props {
isMobile: boolean; isMobile: boolean;
screenSize: Screen; screenSize: Screen;
width: number;
} }
export const useScreenDimensions = (): Props => { export const useScreenDimensions = (): Props => {
const { width } = useResize(); const { width } = useResize();
return useMemo( const isMobile = width < parseInt(theme.screens.md);
() => ({ const screenSize = Object.entries(theme.screens).reduce(
width, (agg: Screen, entry) => {
isMobile: width < parseInt(theme.screens.md),
screenSize: Object.entries(theme.screens).reduce((agg: Screen, entry) => {
if (width > parseInt(entry[1])) { if (width > parseInt(entry[1])) {
agg = entry[0] as Screen; agg = entry[0] as Screen;
} }
return agg; return agg;
}, 'xs'), },
'xs'
);
return useMemo(
() => ({
isMobile,
screenSize,
}), }),
[width] [isMobile, screenSize]
); );
}; };

View File

@ -6,6 +6,7 @@ module.exports = {
lg: '960px', lg: '960px',
xl: '1280px', xl: '1280px',
xxl: '1536px', xxl: '1536px',
xxxl: '1800px',
}, },
colors: { colors: {
transparent: 'transparent', transparent: 'transparent',