feat(trading): navigation (#4375)
This commit is contained in:
parent
8954c41c0a
commit
5f9ec222c1
@ -51,7 +51,8 @@
|
|||||||
"ul": ["list"],
|
"ul": ["list"],
|
||||||
"ol": ["list"]
|
"ol": ["list"]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"no-console": ["error", { "allow": ["warn", "error"] }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -18,10 +18,7 @@ import { VegaWallet } from '../vega-wallet';
|
|||||||
import { useLocation, useMatch } from 'react-router-dom';
|
import { useLocation, useMatch } from 'react-router-dom';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
|
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
|
||||||
import {
|
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
|
||||||
ProtocolUpgradeCountdown,
|
|
||||||
ProtocolUpgradeCountdownMode,
|
|
||||||
} from '@vegaprotocol/proposals';
|
|
||||||
|
|
||||||
export const SettingsLink = () => {
|
export const SettingsLink = () => {
|
||||||
const { open, isOpen, close } = useTelemetryDialog();
|
const { open, isOpen, close } = useTelemetryDialog();
|
||||||
@ -68,9 +65,7 @@ export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
|
|||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<SettingsLink />
|
<SettingsLink />
|
||||||
<ProtocolUpgradeCountdown
|
<ProtocolUpgradeCountdown />
|
||||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -20,6 +20,6 @@ export const downloadJson = (jsonString: string, proposalTitle: string) => {
|
|||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -61,8 +61,6 @@ export const RewardsPage = () => {
|
|||||||
error: paramsError,
|
error: paramsError,
|
||||||
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
|
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
|
||||||
|
|
||||||
console.log('params', params);
|
|
||||||
|
|
||||||
const payoutDuration = useMemo(() => {
|
const payoutDuration = useMemo(() => {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -535,6 +535,7 @@ function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
|
|||||||
// unexpected latency
|
// unexpected latency
|
||||||
const minBefore = subSeconds(new Date(), 5);
|
const minBefore = subSeconds(new Date(), 5);
|
||||||
const maxAfter = addSeconds(new Date(), 5);
|
const maxAfter = addSeconds(new Date(), 5);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(maxAfter);
|
console.log(maxAfter);
|
||||||
const date = new Date($dateTime.toString());
|
const date = new Date($dateTime.toString());
|
||||||
expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal(
|
expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal(
|
||||||
|
@ -68,22 +68,4 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
cy.getByTestId('connect').click();
|
cy.getByTestId('connect').click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Network switcher', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.visit('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('switch to fairground network and check status & incidents link', () => {
|
|
||||||
// 0006-NETW-002
|
|
||||||
// 0006-NETW-003
|
|
||||||
cy.getByTestId('navigation')
|
|
||||||
.find('[data-testid="network-switcher"]')
|
|
||||||
.should('have.text', 'Custom')
|
|
||||||
.click();
|
|
||||||
cy.getByTestId('network-item').contains('Fairground testnet');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -84,7 +84,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
|||||||
.find('button')
|
.find('button')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
const dropdownContent = '[data-testid="proposal-actions-content"]';
|
||||||
const dropdownContentItem = '[role="menuitem"]';
|
const dropdownContentItem = '[role="menuitem"]';
|
||||||
|
|
||||||
// 6001-MARK-059
|
// 6001-MARK-059
|
||||||
@ -100,7 +100,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
|||||||
'VEGA_TOKEN_URL'
|
'VEGA_TOKEN_URL'
|
||||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||||
);
|
);
|
||||||
cy.getByTestId('market-actions-content').click();
|
cy.getByTestId('proposal-actions-content').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6001-MARK-060
|
// 6001-MARK-060
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
import { mockConnectWallet } from '@vegaprotocol/cypress';
|
|
||||||
|
|
||||||
describe('Navbar', { tags: '@smoke' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clearAllLocalStorage();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.visit('/');
|
|
||||||
cy.wait('@Markets');
|
|
||||||
cy.wait('@MarketsData');
|
|
||||||
});
|
|
||||||
|
|
||||||
const pages = [
|
|
||||||
{ name: 'Markets', link: '#/markets/all' },
|
|
||||||
{ name: 'Trading', link: '#/markets' },
|
|
||||||
{ name: 'Portfolio', link: '#/portfolio' },
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('desktop view', () => {
|
|
||||||
pages.forEach(({ name, link }) => {
|
|
||||||
it(`${name} should be correctly rendered`, () => {
|
|
||||||
cy.get('nav')
|
|
||||||
.find(`a[data-testid=${name}]:visible`)
|
|
||||||
.then((element) => {
|
|
||||||
cy.wrap(element).click();
|
|
||||||
cy.location('hash').should('contain', link);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Resources dropdown should be correctly rendered', () => {
|
|
||||||
const resourceSelector = 'ul li:contains(Resources)';
|
|
||||||
['Docs', 'Give Feedback'].forEach((text, index) => {
|
|
||||||
cy.get('nav').find(resourceSelector).contains('Resources').click();
|
|
||||||
cy.get('nav')
|
|
||||||
.find(resourceSelector)
|
|
||||||
.find('.navigation-content li')
|
|
||||||
.eq(index)
|
|
||||||
.find('a')
|
|
||||||
.then((element) => {
|
|
||||||
expect(element.attr('target')).to.eq('_blank');
|
|
||||||
expect(element.attr('href')).to.not.be.empty;
|
|
||||||
expect(element.text()).to.eq(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Disclaimer should be presented after choosing from menu', () => {
|
|
||||||
cy.get('nav')
|
|
||||||
.find('ul li:contains(Resources)')
|
|
||||||
.contains('Resources')
|
|
||||||
.click();
|
|
||||||
cy.getByTestId('Disclaimer').eq(0).click();
|
|
||||||
cy.location('hash').should('equal', '#/disclaimer');
|
|
||||||
cy.get('p').contains(
|
|
||||||
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mobile view', () => {
|
|
||||||
const viewportHeight = Cypress.config('viewportHeight');
|
|
||||||
const viewportWidth = Cypress.config('viewportWidth');
|
|
||||||
before(() => {
|
|
||||||
// a little hack to keep the viewport size between tests (cypress bug)
|
|
||||||
Cypress.config({
|
|
||||||
viewportWidth: 560,
|
|
||||||
viewportHeight: 890,
|
|
||||||
});
|
|
||||||
cy.viewport(560, 890);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('wallet drawer', () => {
|
|
||||||
it('wallet drawer should be correctly rendered', () => {
|
|
||||||
mockConnectWallet();
|
|
||||||
cy.connectVegaWallet(true);
|
|
||||||
cy.getByTestId('connect-vega-wallet-mobile').click();
|
|
||||||
cy.getByTestId('wallets-drawer').should('be.visible');
|
|
||||||
cy.getByTestId('wallets-drawer').within((el) => {
|
|
||||||
cy.wrap(el).get('button').contains('Disconnect').click();
|
|
||||||
});
|
|
||||||
cy.getByTestId('wallets-drawer').should('not.be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('menu drawer', () => {
|
|
||||||
pages.forEach(({ name, link }) => {
|
|
||||||
it(`${name} should be correctly rendered`, () => {
|
|
||||||
cy.getByTestId('button-menu-drawer').click();
|
|
||||||
cy.getByTestId('menu-drawer').should('be.visible');
|
|
||||||
cy.getByTestId('menu-drawer').within((el) => {
|
|
||||||
cy.wrap(el).getByTestId(name).click();
|
|
||||||
cy.location('hash').should('contain', link);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Menu drawer should not be visible until opened', () => {
|
|
||||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
|
||||||
cy.getByTestId('button-menu-drawer').click();
|
|
||||||
cy.getByTestId('menu-drawer').should('be.visible');
|
|
||||||
cy.getByTestId('button-menu-drawer').click();
|
|
||||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
// a little hack to keep the viewport size between tests (cypress bug)
|
|
||||||
Cypress.config({
|
|
||||||
viewportWidth,
|
|
||||||
viewportHeight,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -171,6 +171,7 @@ describe(
|
|||||||
.invoke('text')
|
.invoke('text')
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
const actualDate = text.slice(0, -67);
|
const actualDate = text.slice(0, -67);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(actualDate);
|
console.log(actualDate);
|
||||||
const actualOhlc = text.slice(-67);
|
const actualOhlc = text.slice(-67);
|
||||||
assert.isTrue(expectedDateRegex.test(actualDate));
|
assert.isTrue(expectedDateRegex.test(actualDate));
|
||||||
|
@ -7,7 +7,7 @@ const closePosition = 'close-position';
|
|||||||
const dialogCloseX = 'dialog-close';
|
const dialogCloseX = 'dialog-close';
|
||||||
const dialogContent = 'dialog-content';
|
const dialogContent = 'dialog-content';
|
||||||
const dropDownMenu = 'dropdown-menu';
|
const dropDownMenu = 'dropdown-menu';
|
||||||
const marketActionsContent = 'market-actions-content';
|
const marketActionsContent = 'position-actions-content';
|
||||||
const positions = 'Positions';
|
const positions = 'Positions';
|
||||||
const tabPositions = 'tab-positions';
|
const tabPositions = 'tab-positions';
|
||||||
const toastContent = 'toast-content';
|
const toastContent = 'toast-content';
|
||||||
|
@ -88,9 +88,12 @@ export const TradePanels = ({
|
|||||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||||
{Object.keys(TradingViews).map((key) => {
|
{Object.keys(TradingViews).map((key) => {
|
||||||
const isActive = view === key;
|
const isActive = view === key;
|
||||||
const className = classNames('p-4 min-w-[100px] capitalize', {
|
const className = classNames(
|
||||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
'py-2 px-4 min-w-[100px] capitalize text-sm',
|
||||||
});
|
{
|
||||||
|
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
||||||
|
}
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
data-testid={key}
|
data-testid={key}
|
||||||
|
@ -17,7 +17,7 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<header className={headerClasses}>
|
<header className={headerClasses}>
|
||||||
<div className="flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
<div className="hidden lg:flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div data-testid="header-summary" className="min-w-0">
|
<div data-testid="header-summary" className="min-w-0">
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export const WalletIcon = ({ className }: { className?: string }) => {
|
export const WalletIcon = ({ className }: { className?: string }) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="26"
|
width="26"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 26 18"
|
viewBox="0 0 26 18"
|
||||||
className={className}
|
className={classNames('fill-current', className)}
|
||||||
data-testid="wallet-icon"
|
data-testid="wallet-icon"
|
||||||
>
|
>
|
||||||
<path d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z" />
|
<path d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z" />
|
||||||
|
@ -11,9 +11,9 @@ export const LayoutWithSidebar = () => {
|
|||||||
|
|
||||||
const gridClasses = classNames(
|
const gridClasses = classNames(
|
||||||
'h-full relative z-0 grid',
|
'h-full relative z-0 grid',
|
||||||
'grid-rows-[min-content_1fr]',
|
'grid-rows-[min-content_1fr_40px]',
|
||||||
'grid-cols-[1fr_45px]',
|
'lg:grid-rows-[min-content_1fr]',
|
||||||
'lg:grid-cols-[1fr_350px_45px]'
|
'lg:grid-cols-[1fr_350px_40px]'
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -40,7 +40,14 @@ export const LayoutWithSidebar = () => {
|
|||||||
>
|
>
|
||||||
<SidebarContent />
|
<SidebarContent />
|
||||||
</aside>
|
</aside>
|
||||||
<div className="col-start-2 lg:col-start-3 bg-vega-clight-800 dark:bg-vega-cdark-800 border-l border-default">
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'bg-vega-clight-800 dark:bg-vega-cdark-800',
|
||||||
|
'border-t lg:border-l lg:border-t-0 border-default',
|
||||||
|
'row-start-3 col-start-1 cols-span-full',
|
||||||
|
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,8 +42,6 @@ export const LiquidityHeader = () => {
|
|||||||
triggeringRatio,
|
triggeringRatio,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(market);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
title={
|
title={
|
||||||
|
@ -4,10 +4,12 @@ import { useParams } from 'react-router-dom';
|
|||||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||||
import { useMarket } from '@vegaprotocol/markets';
|
import { useMarket } from '@vegaprotocol/markets';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
export const MarketHeader = () => {
|
export const MarketHeader = () => {
|
||||||
const { marketId } = useParams();
|
const { marketId } = useParams();
|
||||||
const { data } = useMarket(marketId);
|
const { data } = useMarket(marketId);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
@ -15,6 +17,8 @@ export const MarketHeader = () => {
|
|||||||
<Header
|
<Header
|
||||||
title={
|
title={
|
||||||
<Popover
|
<Popover
|
||||||
|
open={open}
|
||||||
|
onChange={setOpen}
|
||||||
trigger={
|
trigger={
|
||||||
<HeaderTitle>
|
<HeaderTitle>
|
||||||
{data.tradableInstrument.instrument.code}
|
{data.tradableInstrument.instrument.code}
|
||||||
@ -23,7 +27,10 @@ export const MarketHeader = () => {
|
|||||||
}
|
}
|
||||||
alignOffset={-10}
|
alignOffset={-10}
|
||||||
>
|
>
|
||||||
<MarketSelector currentMarketId={marketId} />
|
<MarketSelector
|
||||||
|
currentMarketId={marketId}
|
||||||
|
onSelect={() => setOpen(false)}
|
||||||
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -98,6 +98,7 @@ describe('MarketSelectorItem', () => {
|
|||||||
market={market}
|
market={market}
|
||||||
currentMarketId={market.id}
|
currentMarketId={market.id}
|
||||||
style={{}}
|
style={{}}
|
||||||
|
onSelect={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
@ -17,10 +17,12 @@ export const MarketSelectorItem = ({
|
|||||||
market,
|
market,
|
||||||
style,
|
style,
|
||||||
currentMarketId,
|
currentMarketId,
|
||||||
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
market: MarketMaybeWithDataAndCandles;
|
market: MarketMaybeWithDataAndCandles;
|
||||||
style: CSSProperties;
|
style: CSSProperties;
|
||||||
currentMarketId?: string;
|
currentMarketId?: string;
|
||||||
|
onSelect: (marketId: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div style={style} role="row">
|
<div style={style} role="row">
|
||||||
@ -32,6 +34,7 @@ export const MarketSelectorItem = ({
|
|||||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||||
market.id === currentMarketId,
|
market.id === currentMarketId,
|
||||||
})}
|
})}
|
||||||
|
onClick={() => onSelect(market.id)}
|
||||||
>
|
>
|
||||||
<MarketData market={market} />
|
<MarketData market={market} />
|
||||||
</Link>
|
</Link>
|
||||||
@ -80,7 +83,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-2/5" role="gridcell">
|
<div className="w-2/5" role="gridcell">
|
||||||
<h3 className="text-ellipsis whitespace-nowrap overflow-hidden">
|
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||||
{market.tradableInstrument.instrument.code}
|
{market.tradableInstrument.instrument.code}
|
||||||
</h3>
|
</h3>
|
||||||
{mode && (
|
{mode && (
|
||||||
@ -90,7 +93,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||||
title={instrument.product.settlementAsset.symbol}
|
title={instrument.product.settlementAsset.symbol}
|
||||||
data-testid="market-selector-price"
|
data-testid="market-selector-price"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
@ -98,7 +101,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
|||||||
{price} {instrument.product.settlementAsset.symbol}
|
{price} {instrument.product.settlementAsset.symbol}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||||
title={t('24h vol')}
|
title={t('24h vol')}
|
||||||
data-testid="market-selector-volume"
|
data-testid="market-selector-volume"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
|
@ -137,7 +137,7 @@ describe('MarketSelector', () => {
|
|||||||
it('renders only active markets', () => {
|
it('renders only active markets', () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(
|
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(
|
||||||
@ -148,7 +148,7 @@ describe('MarketSelector', () => {
|
|||||||
it('filters by product type', async () => {
|
it('filters by product type', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ describe('MarketSelector', () => {
|
|||||||
it('filters by search term', async () => {
|
it('filters by search term', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ describe('MarketSelector', () => {
|
|||||||
it('filters by asset', async () => {
|
it('filters by asset', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ describe('MarketSelector', () => {
|
|||||||
it('sorts by gained', async () => {
|
it('sorts by gained', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ describe('MarketSelector', () => {
|
|||||||
it('sorts by lost', async () => {
|
it('sorts by lost', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ describe('MarketSelector', () => {
|
|||||||
it('sorts by new', async () => {
|
it('sorts by new', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MarketSelector currentMarketId="market-0" />
|
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export const MarketSelector = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
currentMarketId?: string;
|
currentMarketId?: string;
|
||||||
onSelect?: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [filter, setFilter] = useState<Filter>({
|
const [filter, setFilter] = useState<Filter>({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
@ -48,7 +48,7 @@ export const MarketSelector = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="market-selector">
|
<div data-testid="market-selector">
|
||||||
<div className="pt-2 px-2 mb-2 w-[320px] lg:w-[584px]">
|
<div className="pt-2 px-2 mb-2">
|
||||||
<ProductSelector
|
<ProductSelector
|
||||||
product={filter.product}
|
product={filter.product}
|
||||||
onSelect={(product) => {
|
onSelect={(product) => {
|
||||||
@ -147,16 +147,17 @@ const MarketList = ({
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
currentMarketId?: string;
|
currentMarketId?: string;
|
||||||
onSelect?: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
noItems: string;
|
noItems: string;
|
||||||
}) => {
|
}) => {
|
||||||
const itemSize = 45;
|
const itemSize = 45;
|
||||||
const listRef = useRef<HTMLDivElement | null>(null);
|
const listRef = useRef<HTMLDivElement | null>(null);
|
||||||
const rect = listRef.current?.getBoundingClientRect();
|
const rect = listRef.current?.getBoundingClientRect();
|
||||||
// allow virtualized list to grow until it runs out of space
|
// allow virtualized list to grow until it runs out of space
|
||||||
const height = rect
|
const computedHeight = rect
|
||||||
? Math.min(data.length * itemSize, window.innerHeight - rect.y)
|
? Math.min(data.length * itemSize, window.innerHeight - rect.y)
|
||||||
: 400;
|
: 400;
|
||||||
|
const height = Math.max(computedHeight, 45);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>{error.message}</div>;
|
return <div>{error.message}</div>;
|
||||||
@ -199,7 +200,7 @@ const MarketList = ({
|
|||||||
|
|
||||||
interface ListItemData {
|
interface ListItemData {
|
||||||
data: MarketMaybeWithDataAndCandles[];
|
data: MarketMaybeWithDataAndCandles[];
|
||||||
onSelect?: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
currentMarketId?: string;
|
currentMarketId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +217,7 @@ const ListItem = ({
|
|||||||
market={data.data[index]}
|
market={data.data[index]}
|
||||||
currentMarketId={data.currentMarketId}
|
currentMarketId={data.currentMarketId}
|
||||||
style={style}
|
style={style}
|
||||||
|
onSelect={data.onSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -252,7 +254,11 @@ const List = ({
|
|||||||
|
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height }} data-testid="no-items">
|
<div
|
||||||
|
style={{ height }}
|
||||||
|
className="flex items-center"
|
||||||
|
data-testid="no-items"
|
||||||
|
>
|
||||||
<div className="mx-4 my-2 text-sm">{noItems}</div>
|
<div className="mx-4 my-2 text-sm">{noItems}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './navbar';
|
export * from './navbar';
|
||||||
|
export * from './nav-header';
|
||||||
|
66
apps/trading/components/navbar/nav-header.tsx
Normal file
66
apps/trading/components/navbar/nav-header.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { MarketSelector } from '../market-selector';
|
||||||
|
import { useMarket } from '@vegaprotocol/markets';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is only rendered for the mobile navigation
|
||||||
|
*/
|
||||||
|
export const NavHeader = () => {
|
||||||
|
const { marketId } = useParams();
|
||||||
|
const { data } = useMarket(marketId);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
if (!marketId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullScreenPopover
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(x) => {
|
||||||
|
setOpen(x);
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
|
||||||
|
{data ? data.tradableInstrument.instrument.code : t('Select market')}
|
||||||
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||||
|
</h1>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MarketSelector
|
||||||
|
currentMarketId={marketId}
|
||||||
|
onSelect={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
</FullScreenPopover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||||
|
trigger: React.ReactNode | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FullScreenPopover = ({
|
||||||
|
trigger,
|
||||||
|
children,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: PopoverProps) => {
|
||||||
|
return (
|
||||||
|
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||||
|
<PopoverPrimitive.Trigger data-testid="popover-trigger">
|
||||||
|
{trigger}
|
||||||
|
</PopoverPrimitive.Trigger>
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
data-testid="popover-content"
|
||||||
|
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 text-default border border-default"
|
||||||
|
sideOffset={5}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</PopoverPrimitive.Content>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
</PopoverPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
@ -1,42 +1,158 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, within } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import { Navbar } from './navbar';
|
import { Navbar } from './navbar';
|
||||||
|
import { useGlobalStore } from '../../stores';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/proposals', () => ({
|
||||||
|
ProtocolUpgradeCountdown: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Navbar', () => {
|
describe('Navbar', () => {
|
||||||
const pubKey = 'pubKey';
|
const pubKey = '000';
|
||||||
it('should be properly rendered', () => {
|
const pubKeys = [
|
||||||
render(
|
{
|
||||||
<MockedProvider>
|
publicKey: pubKey,
|
||||||
<MemoryRouter>
|
name: 'Pub key 0',
|
||||||
<VegaWalletContext.Provider
|
},
|
||||||
value={{ pubKey } as VegaWalletContextShape}
|
{
|
||||||
>
|
publicKey: '111',
|
||||||
<Navbar theme="dark" />
|
name: 'Pub key 1',
|
||||||
</VegaWalletContext.Provider>
|
},
|
||||||
</MemoryRouter>
|
];
|
||||||
</MockedProvider>
|
const marketId = 'abc';
|
||||||
|
const navbarContent = 'navbar-menu-content';
|
||||||
|
|
||||||
|
const renderComponent = (
|
||||||
|
initialEntries?: string[],
|
||||||
|
walletContext?: Partial<VegaWalletContextShape>
|
||||||
|
) => {
|
||||||
|
const context = {
|
||||||
|
pubKey,
|
||||||
|
pubKeys,
|
||||||
|
selectPubKey: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
...walletContext,
|
||||||
|
} as VegaWalletContextShape;
|
||||||
|
return render(
|
||||||
|
<MemoryRouter initialEntries={initialEntries}>
|
||||||
|
<VegaWalletContext.Provider value={context}>
|
||||||
|
<Navbar />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('Markets')).toBeInTheDocument();
|
};
|
||||||
expect(screen.getByTestId('Trading')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('Portfolio')).toBeInTheDocument();
|
beforeAll(() => {
|
||||||
|
useGlobalStore.setState({ marketId });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be properly rendered', () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
const expectedLinks = [
|
||||||
|
['/', ''],
|
||||||
|
['/markets/all', 'Markets'],
|
||||||
|
[`/markets/${marketId}`, 'Trading'],
|
||||||
|
['/portfolio', 'Portfolio'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const links = screen.getAllByRole('link');
|
||||||
|
|
||||||
|
links.forEach((link, i) => {
|
||||||
|
const [href, text] = expectedLinks[i];
|
||||||
|
expect(link).toHaveAttribute('href', href);
|
||||||
|
expect(link).toHaveTextContent(text);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Markets page route should not match empty market page', () => {
|
it('Markets page route should not match empty market page', () => {
|
||||||
render(
|
renderComponent(['/markets/all']);
|
||||||
<MockedProvider>
|
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
|
||||||
<MemoryRouter initialEntries={['/markets/all']}>
|
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
|
||||||
<VegaWalletContext.Provider
|
'active'
|
||||||
value={{ pubKey } as VegaWalletContextShape}
|
|
||||||
>
|
|
||||||
<Navbar theme="dark" />
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
</MemoryRouter>
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('Markets')).toHaveClass('active');
|
});
|
||||||
expect(screen.getByTestId('Trading')).not.toHaveClass('active');
|
|
||||||
|
it('can open menu and navigate on small screens', async () => {
|
||||||
|
renderComponent();
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Menu' }));
|
||||||
|
|
||||||
|
const menuEl = screen.getByTestId(navbarContent);
|
||||||
|
expect(menuEl).toBeInTheDocument();
|
||||||
|
const menu = within(menuEl);
|
||||||
|
|
||||||
|
const expectedLinks = [
|
||||||
|
['/markets/all', 'Markets'],
|
||||||
|
[`/markets/${marketId}`, 'Trading'],
|
||||||
|
['/portfolio', 'Portfolio'],
|
||||||
|
];
|
||||||
|
const links = menu.getAllByRole('link');
|
||||||
|
links.forEach((link, i) => {
|
||||||
|
const [href, text] = expectedLinks[i];
|
||||||
|
expect(link).toHaveAttribute('href', href);
|
||||||
|
expect(link).toHaveTextContent(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Close menu' }));
|
||||||
|
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can close menu by clicking overlay', async () => {
|
||||||
|
renderComponent();
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Menu' }));
|
||||||
|
expect(screen.getByTestId(navbarContent)).toBeInTheDocument();
|
||||||
|
await userEvent.click(screen.getByTestId('navbar-menu-overlay'));
|
||||||
|
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can open wallet menu on small screens and change pubkey', async () => {
|
||||||
|
const mockSelectPubKey = jest.fn();
|
||||||
|
renderComponent(undefined, { selectPubKey: mockSelectPubKey });
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||||
|
|
||||||
|
const menuEl = screen.getByTestId(navbarContent);
|
||||||
|
expect(menuEl).toBeInTheDocument();
|
||||||
|
const menu = within(menuEl);
|
||||||
|
|
||||||
|
expect(menu.getAllByTestId(/key-\d+-mobile/)).toHaveLength(pubKeys.length);
|
||||||
|
|
||||||
|
const activeKey = within(menu.getByTestId('key-000-mobile'));
|
||||||
|
expect(activeKey.getByText(pubKeys[0].name)).toBeInTheDocument();
|
||||||
|
expect(activeKey.getByTestId('icon-tick')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const inactiveKey = within(menu.getByTestId('key-111-mobile'));
|
||||||
|
await userEvent.click(inactiveKey.getByText(pubKeys[1].name));
|
||||||
|
expect(mockSelectPubKey).toHaveBeenCalledWith(pubKeys[1].publicKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can transfer and close menu', async () => {
|
||||||
|
renderComponent();
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||||
|
|
||||||
|
const menuEl = screen.getByTestId(navbarContent);
|
||||||
|
expect(menuEl).toBeInTheDocument();
|
||||||
|
const menu = within(menuEl);
|
||||||
|
|
||||||
|
await userEvent.click(menu.getByText('Transfer'));
|
||||||
|
|
||||||
|
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can disconnect and close menu', async () => {
|
||||||
|
const mockDisconnect = jest.fn();
|
||||||
|
renderComponent(undefined, { disconnect: mockDisconnect });
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||||
|
|
||||||
|
const menuEl = screen.getByTestId(navbarContent);
|
||||||
|
expect(menuEl).toBeInTheDocument();
|
||||||
|
const menu = within(menuEl);
|
||||||
|
|
||||||
|
await userEvent.click(menu.getByText('Disconnect'));
|
||||||
|
|
||||||
|
expect(mockDisconnect).toHaveBeenCalled();
|
||||||
|
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,140 +1,400 @@
|
|||||||
import type { ComponentProps, ReactNode } from 'react';
|
import type { ButtonHTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
|
||||||
import {
|
import { useState } from 'react';
|
||||||
DApp,
|
import { useEnvironment, DocsLinks, Networks } from '@vegaprotocol/environment';
|
||||||
NetworkSwitcher,
|
|
||||||
TOKEN_GOVERNANCE,
|
|
||||||
useEnvironment,
|
|
||||||
useLinks,
|
|
||||||
DocsLinks,
|
|
||||||
} from '@vegaprotocol/environment';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||||
import {
|
import { VegaIconNames, VegaIcon, VLogo } from '@vegaprotocol/ui-toolkit';
|
||||||
Navigation,
|
import * as N from '@radix-ui/react-navigation-menu';
|
||||||
NavigationList,
|
import * as D from '@radix-ui/react-dialog';
|
||||||
NavigationItem,
|
import { NavLink } from 'react-router-dom';
|
||||||
NavigationLink,
|
|
||||||
ExternalLink,
|
|
||||||
NavigationBreakpoint,
|
|
||||||
NavigationTrigger,
|
|
||||||
NavigationContent,
|
|
||||||
VegaIconNames,
|
|
||||||
VegaIcon,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
|
|
||||||
import { Links, Routes } from '../../pages/client-router';
|
import { Links, Routes } from '../../pages/client-router';
|
||||||
import {
|
import classNames from 'classnames';
|
||||||
ProtocolUpgradeCountdown,
|
import { VegaWalletMenu } from '../vega-wallet';
|
||||||
ProtocolUpgradeCountdownMode,
|
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
} from '@vegaprotocol/proposals';
|
import { WalletIcon } from '../icons/wallet';
|
||||||
|
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
|
||||||
|
|
||||||
|
type MenuState = 'wallet' | 'nav' | null;
|
||||||
|
type Theme = 'system' | 'yellow';
|
||||||
|
|
||||||
export const Navbar = ({
|
export const Navbar = ({
|
||||||
|
children,
|
||||||
theme = 'system',
|
theme = 'system',
|
||||||
}: {
|
}: {
|
||||||
theme: ComponentProps<typeof Navigation>['theme'];
|
children?: ReactNode;
|
||||||
|
theme?: Theme;
|
||||||
}) => {
|
}) => {
|
||||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
// menu state for small screens
|
||||||
const tokenLink = useLinks(DApp.Token);
|
const [menu, setMenu] = useState<MenuState>(null);
|
||||||
|
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
|
|
||||||
|
const isConnected = pubKey !== null;
|
||||||
|
|
||||||
|
const navTextClasses = 'text-vega-clight-200 dark:text-vega-cdark-200';
|
||||||
|
const rootClasses = classNames(
|
||||||
|
navTextClasses,
|
||||||
|
'flex gap-3 h-10 pr-1',
|
||||||
|
'border-b border-default',
|
||||||
|
'bg-vega-clight-800 dark:bg-vega-cdark-800'
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<N.Root className={rootClasses}>
|
||||||
|
<NavLink
|
||||||
|
to="/"
|
||||||
|
className={classNames('flex items-center px-3', {
|
||||||
|
'bg-vega-yellow text-vega-clight-50': theme === 'yellow',
|
||||||
|
'text-default': theme === 'system',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<VLogo className="w-4" />
|
||||||
|
</NavLink>
|
||||||
|
{/* Left section */}
|
||||||
|
<div className="lg:hidden flex items-center">{children}</div>
|
||||||
|
{/* Used to show header in nav on mobile */}
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<NavbarMenu onClick={() => setMenu(null)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right section */}
|
||||||
|
<div className="ml-auto flex justify-end items-center gap-2">
|
||||||
|
<ProtocolUpgradeCountdown />
|
||||||
|
<NavbarMobileButton
|
||||||
|
onClick={() => {
|
||||||
|
if (isConnected) {
|
||||||
|
setMenu((x) => (x === 'wallet' ? null : 'wallet'));
|
||||||
|
} else {
|
||||||
|
openVegaWalletDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-testid="navbar-mobile-wallet"
|
||||||
|
>
|
||||||
|
<span className="sr-only">{t('Wallet')}</span>
|
||||||
|
<WalletIcon className="w-6" />
|
||||||
|
</NavbarMobileButton>
|
||||||
|
<NavbarMobileButton
|
||||||
|
onClick={() => {
|
||||||
|
setMenu((x) => (x === 'nav' ? null : 'nav'));
|
||||||
|
}}
|
||||||
|
data-testid="navbar-mobile-burger"
|
||||||
|
>
|
||||||
|
<span className="sr-only">{t('Menu')}</span>
|
||||||
|
<BurgerIcon />
|
||||||
|
</NavbarMobileButton>
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<VegaWalletConnectButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{menu !== null && (
|
||||||
|
<D.Root
|
||||||
|
open={menu !== null}
|
||||||
|
onOpenChange={(open) => setMenu((x) => (open ? x : null))}
|
||||||
|
>
|
||||||
|
<D.Overlay
|
||||||
|
className="lg:hidden fixed inset-0 dark:bg-black/80 bg-black/50 z-20"
|
||||||
|
data-testid="navbar-menu-overlay"
|
||||||
|
/>
|
||||||
|
<D.Content
|
||||||
|
className={classNames(
|
||||||
|
'lg:hidden',
|
||||||
|
'fixed top-0 right-0 z-20 w-3/4 h-screen border-l border-default bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||||
|
navTextClasses
|
||||||
|
)}
|
||||||
|
data-testid="navbar-menu-content"
|
||||||
|
>
|
||||||
|
<div className="flex justify-end items-center h-10 p-1">
|
||||||
|
<NavbarMobileButton onClick={() => setMenu(null)}>
|
||||||
|
<span className="sr-only">{t('Close menu')}</span>
|
||||||
|
<VegaIcon name={VegaIconNames.CROSS} size={24} />
|
||||||
|
</NavbarMobileButton>
|
||||||
|
</div>
|
||||||
|
{menu === 'nav' && <NavbarMenu onClick={() => setMenu(null)} />}
|
||||||
|
{menu === 'wallet' && <VegaWalletMenu setMenu={setMenu} />}
|
||||||
|
</D.Content>
|
||||||
|
</D.Root>
|
||||||
|
)}
|
||||||
|
</N.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of links or dropdown triggers to show in the main section
|
||||||
|
* of the navigation
|
||||||
|
*/
|
||||||
|
const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||||
|
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||||
const marketId = useGlobalStore((store) => store.marketId);
|
const marketId = useGlobalStore((store) => store.marketId);
|
||||||
|
|
||||||
|
// If we have a stored marketId make Trade link go to that market
|
||||||
|
// otherwise always go to /markets/all
|
||||||
const tradingPath = marketId
|
const tradingPath = marketId
|
||||||
? Links[Routes.MARKET](marketId)
|
? Links[Routes.MARKET](marketId)
|
||||||
: Links[Routes.MARKET]();
|
: Links[Routes.MARKET]('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navigation
|
<div className="lg:flex lg:h-full gap-3">
|
||||||
appName="console"
|
<NavbarList>
|
||||||
theme={theme}
|
<NavbarItem>
|
||||||
actions={
|
<NavbarTrigger data-testid="navbar-network-switcher-trigger">
|
||||||
<>
|
{envNameMapping[VEGA_ENV]}
|
||||||
<ProtocolUpgradeCountdown
|
</NavbarTrigger>
|
||||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
<NavbarContent data-testid="navbar-content-network-switcher">
|
||||||
/>
|
<ul className="lg:p-4">
|
||||||
<VegaWalletConnectButton />
|
{[Networks.MAINNET, Networks.TESTNET].map((n) => {
|
||||||
</>
|
const url = VEGA_NETWORKS[n];
|
||||||
}
|
if (!url) return;
|
||||||
breakpoints={[521, 1122]}
|
return (
|
||||||
>
|
<NavbarSubItem key={n}>
|
||||||
<NavigationList
|
<NavbarLink to={url}>{envNameMapping[n]}</NavbarLink>
|
||||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
</NavbarSubItem>
|
||||||
hide={[NavigationBreakpoint.Small]}
|
);
|
||||||
>
|
})}
|
||||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
</ul>
|
||||||
<NetworkSwitcher className="[.drawer-content_&]:w-full" />
|
</NavbarContent>
|
||||||
</NavigationItem>
|
</NavbarItem>
|
||||||
</NavigationList>
|
</NavbarList>
|
||||||
<NavigationList
|
<NavbarListDivider />
|
||||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
<NavbarList>
|
||||||
>
|
<NavbarItem>
|
||||||
<NavigationItem>
|
<NavbarLink to={Links[Routes.MARKETS]()} onClick={onClick}>
|
||||||
<NavigationLink data-testid="Markets" to={Links[Routes.MARKETS]()}>
|
|
||||||
{t('Markets')}
|
{t('Markets')}
|
||||||
</NavigationLink>
|
</NavbarLink>
|
||||||
</NavigationItem>
|
</NavbarItem>
|
||||||
<NavigationItem>
|
<NavbarItem>
|
||||||
<NavigationLink data-testid="Trading" to={tradingPath} end>
|
<NavbarLink to={tradingPath} onClick={onClick}>
|
||||||
{t('Trading')}
|
{t('Trading')}
|
||||||
</NavigationLink>
|
</NavbarLink>
|
||||||
</NavigationItem>
|
</NavbarItem>
|
||||||
<NavigationItem>
|
<NavbarItem>
|
||||||
<NavigationLink
|
<NavbarLink to={Links[Routes.PORTFOLIO]()} onClick={onClick}>
|
||||||
data-testid="Portfolio"
|
|
||||||
to={Links[Routes.PORTFOLIO]()}
|
|
||||||
>
|
|
||||||
{t('Portfolio')}
|
{t('Portfolio')}
|
||||||
</NavigationLink>
|
</NavbarLink>
|
||||||
</NavigationItem>
|
</NavbarItem>
|
||||||
<NavigationItem>
|
<NavbarItem>
|
||||||
<NavExternalLink href={tokenLink(TOKEN_GOVERNANCE)}>
|
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
||||||
{t('Governance')}
|
<NavbarContent data-testid="navbar-content-resources">
|
||||||
</NavExternalLink>
|
<ul className="lg:p-4">
|
||||||
</NavigationItem>
|
{DocsLinks?.NEW_TO_VEGA && (
|
||||||
{DocsLinks?.NEW_TO_VEGA && GITHUB_FEEDBACK_URL && (
|
<NavbarSubItem>
|
||||||
<NavigationItem>
|
<NavbarLinkExternal to={DocsLinks?.NEW_TO_VEGA}>
|
||||||
<NavigationTrigger>{t('Resources')}</NavigationTrigger>
|
|
||||||
<NavigationContent>
|
|
||||||
<NavigationList>
|
|
||||||
<NavigationItem>
|
|
||||||
<NavExternalLink href={DocsLinks.NEW_TO_VEGA}>
|
|
||||||
{t('Docs')}
|
{t('Docs')}
|
||||||
</NavExternalLink>
|
</NavbarLinkExternal>
|
||||||
</NavigationItem>
|
</NavbarSubItem>
|
||||||
<NavigationItem>
|
)}
|
||||||
<NavExternalLink href={GITHUB_FEEDBACK_URL}>
|
{GITHUB_FEEDBACK_URL && (
|
||||||
|
<NavbarSubItem>
|
||||||
|
<NavbarLinkExternal to={GITHUB_FEEDBACK_URL}>
|
||||||
{t('Give Feedback')}
|
{t('Give Feedback')}
|
||||||
</NavExternalLink>
|
</NavbarLinkExternal>
|
||||||
</NavigationItem>
|
</NavbarSubItem>
|
||||||
<NavigationItem>
|
)}
|
||||||
<NavigationLink
|
<NavbarSubItem>
|
||||||
data-testid="Disclaimer"
|
<NavbarLink to={Links[Routes.DISCLAIMER]()} onClick={onClick}>
|
||||||
to={Links[Routes.DISCLAIMER]()}
|
{t('Disclaimer')}
|
||||||
>
|
</NavbarLink>
|
||||||
{t('Disclaimer')}
|
</NavbarSubItem>
|
||||||
</NavigationLink>
|
</ul>
|
||||||
</NavigationItem>
|
</NavbarContent>
|
||||||
</NavigationList>
|
</NavbarItem>
|
||||||
</NavigationContent>
|
</NavbarList>
|
||||||
</NavigationItem>
|
</div>
|
||||||
)}
|
|
||||||
</NavigationList>
|
|
||||||
</Navigation>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavExternalLink = ({
|
/**
|
||||||
|
* Wrapper for radix-ux Trigger for consistent styles
|
||||||
|
*/
|
||||||
|
const NavbarTrigger = ({
|
||||||
children,
|
children,
|
||||||
href,
|
...props
|
||||||
|
}: N.NavigationMenuTriggerProps) => {
|
||||||
|
return (
|
||||||
|
<N.Trigger
|
||||||
|
{...props}
|
||||||
|
onPointerMove={preventHover}
|
||||||
|
onPointerLeave={preventHover}
|
||||||
|
className={classNames(
|
||||||
|
'w-full lg:w-auto lg:h-full',
|
||||||
|
'flex items-center justify-between lg:justify-center gap-2 px-6 py-2 lg:p-0',
|
||||||
|
'text-lg lg:text-sm',
|
||||||
|
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||||
|
</N.Trigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for react-router-dom NavLink for consistent styles
|
||||||
|
*/
|
||||||
|
const NavbarLink = ({
|
||||||
|
children,
|
||||||
|
to,
|
||||||
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
href: string;
|
to: string;
|
||||||
|
onClick?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ExternalLink href={href}>
|
<N.Link asChild={true}>
|
||||||
<span className="flex items-center gap-2">
|
<NavLink
|
||||||
<span>{children}</span>
|
to={to}
|
||||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
className={classNames(
|
||||||
</span>
|
'block lg:flex lg:h-full flex-col justify-center',
|
||||||
</ExternalLink>
|
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||||
|
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{({ isActive }) => {
|
||||||
|
const borderClasses = {
|
||||||
|
'border-b-2': true,
|
||||||
|
'border-transparent': !isActive,
|
||||||
|
'border-vega-yellow lg:group-[.navbar-content]:border-transparent':
|
||||||
|
isActive,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={classNames('lg:border-0', borderClasses, {
|
||||||
|
'text-vega-clight-50 dark:text-vega-cdark-50': isActive,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'hidden lg:block absolute left-0 bottom-0 w-full h-0',
|
||||||
|
borderClasses
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</NavLink>
|
||||||
|
</N.Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NavbarItem = (props: N.NavigationMenuItemProps) => {
|
||||||
|
return <N.Item {...props} className="relative" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavbarSubItem = (props: LiHTMLAttributes<HTMLElement>) => {
|
||||||
|
return <li {...props} className="lg:mb-4 lg:last:mb-0" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavbarList = (props: N.NavigationMenuListProps) => {
|
||||||
|
return <N.List {...props} className="lg:flex lg:h-full gap-6" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content that gets rendered when a sub section of the navbar is shown
|
||||||
|
*/
|
||||||
|
const NavbarContent = (props: N.NavigationMenuContentProps) => {
|
||||||
|
return (
|
||||||
|
<N.Content
|
||||||
|
{...props}
|
||||||
|
className={classNames(
|
||||||
|
'group navbar-content',
|
||||||
|
'lg:absolute lg:mt-2 pl-2 lg:pl-0 z-20 lg:min-w-[290px]',
|
||||||
|
'lg:bg-vega-clight-700 lg:dark:bg-vega-cdark-700',
|
||||||
|
'lg:border border-vega-clight-500 dark:border-vega-cdark-500 lg:rounded'
|
||||||
|
)}
|
||||||
|
onPointerEnter={preventHover}
|
||||||
|
onPointerLeave={preventHover}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NavbarLink with OPEN_EXTERNAL icon
|
||||||
|
*/
|
||||||
|
const NavbarLinkExternal = ({
|
||||||
|
children,
|
||||||
|
to,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
to: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<N.Link asChild={true}>
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
className={classNames(
|
||||||
|
'flex lg:inline-flex gap-2 justify-between items-center relative',
|
||||||
|
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||||
|
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<span>{children}</span>
|
||||||
|
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||||
|
</NavLink>
|
||||||
|
</N.Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BurgerIcon = () => (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
className="w-full stroke-current"
|
||||||
|
>
|
||||||
|
<line x1={0.5} x2={15.5} y1={3.5} y2={3.5} />
|
||||||
|
<line x1={0.5} x2={15.5} y1={11.5} y2={11.5} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NavbarListDivider = () => {
|
||||||
|
return (
|
||||||
|
<div className="py-2 px-6 lg:px-0" role="separator">
|
||||||
|
<div className="h-px lg:h-full w-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button component to avoid repeating styles for buttons shown on small screens
|
||||||
|
*/
|
||||||
|
const NavbarMobileButton = (props: ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
className={classNames(
|
||||||
|
'w-8 h-8 lg:hidden flex items-center p-1 rounded ',
|
||||||
|
'hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500',
|
||||||
|
'hover:text-vega-clight-50 dark:hover:text-vega-cdark-50'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const envNameMapping: Record<Networks, string> = {
|
||||||
|
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
|
||||||
|
[Networks.CUSTOM]: t('Custom'),
|
||||||
|
[Networks.DEVNET]: t('Devnet'),
|
||||||
|
[Networks.STAGNET1]: t('Stagnet'),
|
||||||
|
[Networks.TESTNET]: t('Fairground testnet'),
|
||||||
|
[Networks.MAINNET_MIRROR]: t('Mirror'),
|
||||||
|
[Networks.MAINNET]: t('Mainnet'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/radix-ui/primitives/issues/1630
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const preventHover = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
@ -51,9 +51,10 @@ type SidebarView =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
|
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 h-full py-1" data-testid="sidebar">
|
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
||||||
<nav className="flex flex-col items-center gap-4 p-1">
|
<nav className={navClasses}>
|
||||||
{/* sidebar options that always show */}
|
{/* sidebar options that always show */}
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
view={ViewType.Deposit}
|
view={ViewType.Deposit}
|
||||||
@ -102,7 +103,7 @@ export const Sidebar = () => {
|
|||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</nav>
|
</nav>
|
||||||
<nav className="mt-auto flex flex-col items-center gap-4 p-1">
|
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
view={ViewType.Settings}
|
view={ViewType.Settings}
|
||||||
icon={VegaIconNames.COG}
|
icon={VegaIconNames.COG}
|
||||||
@ -161,7 +162,7 @@ const SidebarButton = ({
|
|||||||
const SidebarDivider = () => {
|
const SidebarDivider = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-4 h-px"
|
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-px h-4 lg:w-4 lg:h-px"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,7 @@ describe('VegaWalletConnectButton', () => {
|
|||||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||||
|
|
||||||
const button = screen.getByTestId('connect-vega-wallet');
|
const button = screen.getByTestId('connect-vega-wallet');
|
||||||
expect(button).toHaveTextContent('Connect Vega wallet');
|
expect(button).toHaveTextContent('Connect');
|
||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -1,148 +1,26 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { truncateByChars } from '@vegaprotocol/utils';
|
import { truncateByChars } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuItemIndicator,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Drawer,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
|
TradingButton as Button,
|
||||||
|
Intent,
|
||||||
|
TradingDropdown,
|
||||||
|
TradingDropdownTrigger,
|
||||||
|
TradingDropdownContent,
|
||||||
|
TradingDropdownRadioGroup,
|
||||||
|
TradingDropdownSeparator,
|
||||||
|
TradingDropdownItem,
|
||||||
|
TradingDropdownRadioItem,
|
||||||
|
TradingDropdownItemIndicator,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { PubKey } from '@vegaprotocol/wallet';
|
import type { PubKey } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import { WalletIcon } from '../icons/wallet';
|
|
||||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||||
import { ViewType, useSidebar } from '../sidebar';
|
import { ViewType, useSidebar } from '../sidebar';
|
||||||
|
import classNames from 'classnames';
|
||||||
const MobileWalletButton = ({
|
|
||||||
isConnected,
|
|
||||||
activeKey,
|
|
||||||
}: {
|
|
||||||
isConnected?: boolean;
|
|
||||||
activeKey?: PubKey;
|
|
||||||
}) => {
|
|
||||||
const { pubKeys, selectPubKey, disconnect, fetchPubKeys } = useVegaWallet();
|
|
||||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
|
||||||
(store) => store.openVegaWalletDialog
|
|
||||||
);
|
|
||||||
const setView = useSidebar((store) => store.setView);
|
|
||||||
const { VEGA_ENV } = useEnvironment();
|
|
||||||
const isYellow = VEGA_ENV === Networks.TESTNET;
|
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
||||||
const mobileDisconnect = useCallback(() => {
|
|
||||||
setDrawerOpen(false);
|
|
||||||
disconnect();
|
|
||||||
}, [disconnect]);
|
|
||||||
const openDrawer = useCallback(() => {
|
|
||||||
if (!isConnected) {
|
|
||||||
openVegaWalletDialog();
|
|
||||||
setDrawerOpen(false);
|
|
||||||
} else {
|
|
||||||
if (fetchPubKeys) {
|
|
||||||
fetchPubKeys();
|
|
||||||
}
|
|
||||||
setDrawerOpen(!drawerOpen);
|
|
||||||
}
|
|
||||||
}, [drawerOpen, fetchPubKeys, isConnected, openVegaWalletDialog]);
|
|
||||||
|
|
||||||
const iconClass = drawerOpen
|
|
||||||
? 'hidden'
|
|
||||||
: isYellow
|
|
||||||
? 'fill-black'
|
|
||||||
: 'fill-white';
|
|
||||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
const walletButton = (
|
|
||||||
<button
|
|
||||||
className="my-2 transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]"
|
|
||||||
onClick={openDrawer}
|
|
||||||
data-testid="connect-vega-wallet-mobile"
|
|
||||||
>
|
|
||||||
<WalletIcon className={iconClass} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
const onSelectItem = useCallback(
|
|
||||||
(pubkey: string) => {
|
|
||||||
setDrawerOpen(false);
|
|
||||||
selectPubKey(pubkey);
|
|
||||||
},
|
|
||||||
[selectPubKey]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="lg:hidden overflow-hidden flex" ref={setContainer}>
|
|
||||||
<Drawer
|
|
||||||
dataTestId="wallets-drawer"
|
|
||||||
open={drawerOpen}
|
|
||||||
onChange={setDrawerOpen}
|
|
||||||
container={container}
|
|
||||||
trigger={walletButton}
|
|
||||||
>
|
|
||||||
<div className="border-l border-default p-2 gap-4 flex flex-col w-full h-full bg-white dark:bg-black dark:text-white justify-between">
|
|
||||||
<div className="flex h-5 justify-end">
|
|
||||||
<button
|
|
||||||
className="transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]"
|
|
||||||
onClick={() => setDrawerOpen(false)}
|
|
||||||
data-testid="connect-vega-wallet-mobile-close"
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'w-[26px] h-[2px] bg-black dark:bg-white transition-all translate-y-[7.5px] rotate-45',
|
|
||||||
{
|
|
||||||
hidden: !drawerOpen,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'w-[26px] h-[2px] bg-black dark:bg-white transition-all -translate-y-[7.5px] -rotate-45',
|
|
||||||
{
|
|
||||||
hidden: !drawerOpen,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="grow my-4" role="list">
|
|
||||||
{(pubKeys || []).map((pk) => (
|
|
||||||
<KeypairListItem
|
|
||||||
key={pk.publicKey}
|
|
||||||
pk={pk}
|
|
||||||
isActive={activeKey?.publicKey === pk.publicKey}
|
|
||||||
onSelectItem={onSelectItem}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2 m-4">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setDrawerOpen(false);
|
|
||||||
setView({ type: ViewType.Transfer });
|
|
||||||
}}
|
|
||||||
fill
|
|
||||||
>
|
|
||||||
{t('Transfer')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={mobileDisconnect} fill>
|
|
||||||
{t('Disconnect')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VegaWalletConnectButton = () => {
|
export const VegaWalletConnectButton = () => {
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
@ -166,96 +44,101 @@ export const VegaWalletConnectButton = () => {
|
|||||||
|
|
||||||
if (isConnected && pubKeys) {
|
if (isConnected && pubKeys) {
|
||||||
return (
|
return (
|
||||||
<>
|
<TradingDropdown
|
||||||
<div className="hidden lg:block">
|
open={dropdownOpen}
|
||||||
<DropdownMenu
|
trigger={
|
||||||
open={dropdownOpen}
|
<TradingDropdownTrigger
|
||||||
trigger={
|
data-testid="manage-vega-wallet"
|
||||||
<DropdownMenuTrigger
|
onClick={() => {
|
||||||
data-testid="manage-vega-wallet"
|
if (fetchPubKeys) {
|
||||||
|
fetchPubKeys();
|
||||||
|
}
|
||||||
|
setDropdownOpen(!dropdownOpen);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />}
|
||||||
|
>
|
||||||
|
{activeKey && <span className="uppercase">{activeKey.name}</span>}
|
||||||
|
{' | '}
|
||||||
|
{truncateByChars(pubKey)}
|
||||||
|
</Button>
|
||||||
|
</TradingDropdownTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TradingDropdownContent
|
||||||
|
onInteractOutside={() => setDropdownOpen(false)}
|
||||||
|
sideOffset={12}
|
||||||
|
side="bottom"
|
||||||
|
align="end"
|
||||||
|
onEscapeKeyDown={() => setDropdownOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="min-w-[340px]" data-testid="keypair-list">
|
||||||
|
<TradingDropdownRadioGroup
|
||||||
|
value={pubKey}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
selectPubKey(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pubKeys.map((pk) => (
|
||||||
|
<KeypairItem
|
||||||
|
key={pk.publicKey}
|
||||||
|
pk={pk}
|
||||||
|
active={pk.publicKey === pubKey}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TradingDropdownRadioGroup>
|
||||||
|
<TradingDropdownSeparator />
|
||||||
|
{!isReadOnly && (
|
||||||
|
<TradingDropdownItem
|
||||||
|
data-testid="wallet-transfer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (fetchPubKeys) {
|
setView({ type: ViewType.Transfer });
|
||||||
fetchPubKeys();
|
setDropdownOpen(false);
|
||||||
}
|
|
||||||
setDropdownOpen(!dropdownOpen);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{activeKey && (
|
{t('Transfer')}
|
||||||
<span className="uppercase">{activeKey.name}</span>
|
</TradingDropdownItem>
|
||||||
)}
|
)}
|
||||||
{': '}
|
<TradingDropdownItem data-testid="disconnect" onClick={disconnect}>
|
||||||
{truncateByChars(pubKey)}
|
{t('Disconnect')}
|
||||||
</DropdownMenuTrigger>
|
</TradingDropdownItem>
|
||||||
}
|
</div>
|
||||||
>
|
</TradingDropdownContent>
|
||||||
<DropdownMenuContent
|
</TradingDropdown>
|
||||||
onInteractOutside={() => setDropdownOpen(false)}
|
|
||||||
sideOffset={17}
|
|
||||||
side="bottom"
|
|
||||||
align="end"
|
|
||||||
onEscapeKeyDown={() => setDropdownOpen(false)}
|
|
||||||
>
|
|
||||||
<div className="min-w-[340px]" data-testid="keypair-list">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={pubKey}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
selectPubKey(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pubKeys.map((pk) => (
|
|
||||||
<KeypairItem key={pk.publicKey} pk={pk} />
|
|
||||||
))}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
{!isReadOnly && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
data-testid="wallet-transfer"
|
|
||||||
onClick={() => {
|
|
||||||
setView({ type: ViewType.Transfer });
|
|
||||||
setDropdownOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Transfer')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
|
|
||||||
{t('Disconnect')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
<MobileWalletButton isConnected activeKey={activeKey} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Button
|
||||||
<Button
|
data-testid="connect-vega-wallet"
|
||||||
data-testid="connect-vega-wallet"
|
onClick={openVegaWalletDialog}
|
||||||
onClick={openVegaWalletDialog}
|
size="small"
|
||||||
size="sm"
|
intent={Intent.None}
|
||||||
className="hidden lg:block"
|
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||||
>
|
>
|
||||||
<span className="whitespace-nowrap">{t('Connect Vega wallet')}</span>
|
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<MobileWalletButton />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
const KeypairItem = ({ pk, active }: { pk: PubKey; active: boolean }) => {
|
||||||
const [copied, setCopied] = useCopyTimeout();
|
const [copied, setCopied] = useCopyTimeout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuRadioItem value={pk.publicKey}>
|
<TradingDropdownRadioItem value={pk.publicKey}>
|
||||||
<div className="flex-1 mr-2" data-testid={`key-${pk.publicKey}`}>
|
<div
|
||||||
<span className="mr-2">
|
className={classNames('flex-1 mr-2', {
|
||||||
<span>
|
'text-default': active,
|
||||||
<span className="uppercase">{pk.name}</span>:{' '}
|
'text-muted': !active,
|
||||||
{truncateByChars(pk.publicKey)}
|
})}
|
||||||
</span>
|
data-testid={`key-${pk.publicKey}`}
|
||||||
|
>
|
||||||
|
<span className={classNames('mr-2 uppercase')}>
|
||||||
|
{pk.name}
|
||||||
|
{' | '}
|
||||||
|
{truncateByChars(pk.publicKey)}
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||||
@ -270,46 +153,7 @@ const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
|||||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenuItemIndicator />
|
<TradingDropdownItemIndicator />
|
||||||
</DropdownMenuRadioItem>
|
</TradingDropdownRadioItem>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const KeypairListItem = ({
|
|
||||||
pk,
|
|
||||||
isActive,
|
|
||||||
onSelectItem,
|
|
||||||
}: {
|
|
||||||
pk: PubKey;
|
|
||||||
isActive: boolean;
|
|
||||||
onSelectItem: (pk: string) => void;
|
|
||||||
}) => {
|
|
||||||
const [copied, setCopied] = useCopyTimeout();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
|
||||||
data-testid={`key-${pk.publicKey}-mobile`}
|
|
||||||
>
|
|
||||||
<span className="flex gap-2 items-center mr-2">
|
|
||||||
<button onClick={() => onSelectItem(pk.publicKey)}>
|
|
||||||
<span className="uppercase">{pk.name}</span>
|
|
||||||
</button>
|
|
||||||
{isActive && <VegaIcon name={VegaIconNames.TICK} />}
|
|
||||||
</span>
|
|
||||||
<span className="flex gap-2 items-center">
|
|
||||||
{truncateByChars(pk.publicKey)}{' '}
|
|
||||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
|
||||||
<button
|
|
||||||
data-testid="copy-vega-public-key"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<span className="sr-only">{t('Copy')}</span>
|
|
||||||
<VegaIcon name={VegaIconNames.COPY} />
|
|
||||||
</button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
1
apps/trading/components/vega-wallet/index.ts
Normal file
1
apps/trading/components/vega-wallet/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { VegaWalletMenu } from './vega-wallet-menu';
|
106
apps/trading/components/vega-wallet/vega-wallet-menu.tsx
Normal file
106
apps/trading/components/vega-wallet/vega-wallet-menu.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||||
|
import {
|
||||||
|
TradingButton as Button,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { truncateByChars } from '@vegaprotocol/utils';
|
||||||
|
import { useVegaWallet, type PubKey } from '@vegaprotocol/wallet';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import { ViewType, useSidebar } from '../sidebar';
|
||||||
|
|
||||||
|
export const VegaWalletMenu = ({
|
||||||
|
setMenu,
|
||||||
|
}: {
|
||||||
|
setMenu: (open: 'nav' | 'wallet' | null) => void;
|
||||||
|
}) => {
|
||||||
|
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||||
|
const setView = useSidebar((store) => store.setView);
|
||||||
|
|
||||||
|
const activeKey = useMemo(() => {
|
||||||
|
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||||
|
}, [pubKey, pubKeys]);
|
||||||
|
|
||||||
|
const onSelectItem = useCallback(
|
||||||
|
(pubkey: string) => {
|
||||||
|
selectPubKey(pubkey);
|
||||||
|
},
|
||||||
|
[selectPubKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="grow my-4" role="list">
|
||||||
|
{(pubKeys || []).map((pk) => (
|
||||||
|
<KeypairListItem
|
||||||
|
key={pk.publicKey}
|
||||||
|
pk={pk}
|
||||||
|
isActive={activeKey?.publicKey === pk.publicKey}
|
||||||
|
onSelectItem={onSelectItem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 m-4">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setView({ type: ViewType.Transfer });
|
||||||
|
setMenu(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Transfer')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await disconnect();
|
||||||
|
setMenu(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Disconnect')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const KeypairListItem = ({
|
||||||
|
pk,
|
||||||
|
isActive,
|
||||||
|
onSelectItem,
|
||||||
|
}: {
|
||||||
|
pk: PubKey;
|
||||||
|
isActive: boolean;
|
||||||
|
onSelectItem: (pk: string) => void;
|
||||||
|
}) => {
|
||||||
|
const [copied, setCopied] = useCopyTimeout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||||
|
data-testid={`key-${pk.publicKey}-mobile`}
|
||||||
|
>
|
||||||
|
<span className="flex gap-2 items-center mr-2">
|
||||||
|
<button type="button" onClick={() => onSelectItem(pk.publicKey)}>
|
||||||
|
<span className="uppercase">{pk.name}</span>
|
||||||
|
</button>
|
||||||
|
{isActive && <VegaIcon name={VegaIconNames.TICK} />}
|
||||||
|
</span>
|
||||||
|
<span className="flex gap-2 items-center">
|
||||||
|
{truncateByChars(pk.publicKey)}{' '}
|
||||||
|
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="copy-vega-public-key"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">{t('Copy')}</span>
|
||||||
|
<VegaIcon name={VegaIconNames.COPY} />
|
||||||
|
</button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -16,6 +16,7 @@ import {
|
|||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import {
|
import {
|
||||||
envTriggerMapping,
|
envTriggerMapping,
|
||||||
|
Networks,
|
||||||
NodeSwitcherDialog,
|
NodeSwitcherDialog,
|
||||||
useEnvironment,
|
useEnvironment,
|
||||||
useInitializeEnv,
|
useInitializeEnv,
|
||||||
@ -25,7 +26,13 @@ import './styles.css';
|
|||||||
import { usePageTitleStore } from '../stores';
|
import { usePageTitleStore } from '../stores';
|
||||||
import DialogsContainer from './dialogs-container';
|
import DialogsContainer from './dialogs-container';
|
||||||
import ToastsManager from './toasts-manager';
|
import ToastsManager from './toasts-manager';
|
||||||
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
|
import {
|
||||||
|
HashRouter,
|
||||||
|
useLocation,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useSearchParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
import { Connectors } from '../lib/vega-connectors';
|
import { Connectors } from '../lib/vega-connectors';
|
||||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
@ -39,6 +46,8 @@ import {
|
|||||||
ProtocolUpgradeProposalNotification,
|
ProtocolUpgradeProposalNotification,
|
||||||
} from '@vegaprotocol/proposals';
|
} from '@vegaprotocol/proposals';
|
||||||
import { ViewingBanner } from '../components/viewing-banner';
|
import { ViewingBanner } from '../components/viewing-banner';
|
||||||
|
import { NavHeader } from '../components/navbar/nav-header';
|
||||||
|
import { Routes as AppRoutes } from './client-router';
|
||||||
|
|
||||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||||
|
|
||||||
@ -74,6 +83,7 @@ const InitializeHandlers = () => {
|
|||||||
|
|
||||||
function AppBody({ Component }: AppProps) {
|
function AppBody({ Component }: AppProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const gridClasses = classNames(
|
const gridClasses = classNames(
|
||||||
'h-full relative z-0 grid',
|
'h-full relative z-0 grid',
|
||||||
'grid-rows-[repeat(3,min-content),minmax(0,1fr)]'
|
'grid-rows-[repeat(3,min-content),minmax(0,1fr)]'
|
||||||
@ -87,7 +97,16 @@ function AppBody({ Component }: AppProps) {
|
|||||||
<Title />
|
<Title />
|
||||||
<div className={gridClasses}>
|
<div className={gridClasses}>
|
||||||
<AnnouncementBanner />
|
<AnnouncementBanner />
|
||||||
<Navbar theme="system" />
|
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'}>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path={AppRoutes.MARKETS}
|
||||||
|
// render nothing for markets/all, otherwise markets/:marketId will match with markets/all
|
||||||
|
element={null}
|
||||||
|
/>
|
||||||
|
<Route path={AppRoutes.MARKET} element={<NavHeader />} />
|
||||||
|
</Routes>
|
||||||
|
</Navbar>
|
||||||
<div data-testid="banners">
|
<div data-testid="banners">
|
||||||
<ProtocolUpgradeProposalNotification
|
<ProtocolUpgradeProposalNotification
|
||||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export function createLog(name: string) {
|
export function createLog(name: string) {
|
||||||
return (message: string) => {
|
return (message: string) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(`[${name}]: ${message}`);
|
console.log(`[${name}]: ${message}`);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ export function waitForProposal(id: string): Promise<{ id: string }> {
|
|||||||
resolve(res.proposal);
|
resolve(res.proposal);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick++;
|
tick++;
|
||||||
|
@ -15,6 +15,7 @@ export const addImportNodeWallets = () => {
|
|||||||
.its('stdout')
|
.its('stdout')
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const obj = JSON.parse(result);
|
const obj = JSON.parse(result);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(obj);
|
console.log(obj);
|
||||||
cy.writeFile(
|
cy.writeFile(
|
||||||
'./src/fixtures/wallet/node0RecoveryPhrase',
|
'./src/fixtures/wallet/node0RecoveryPhrase',
|
||||||
|
@ -30,6 +30,7 @@ export const addValidatorsSelfDelegate = () => {
|
|||||||
.its('stdout')
|
.its('stdout')
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const obj = JSON.parse(result);
|
const obj = JSON.parse(result);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(obj);
|
console.log(obj);
|
||||||
cy.writeFile(
|
cy.writeFile(
|
||||||
'./src/fixtures/wallet/node0RecoveryPhrase',
|
'./src/fixtures/wallet/node0RecoveryPhrase',
|
||||||
|
@ -30,8 +30,10 @@ export function addVegaWalletTopUpRewardsPool() {
|
|||||||
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
||||||
transferEndEpoch = transferStartEpoch + 100;
|
transferEndEpoch = transferStartEpoch + 100;
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
console.log(transferStartEpoch);
|
console.log(transferStartEpoch);
|
||||||
console.log(transferEndEpoch);
|
console.log(transferEndEpoch);
|
||||||
|
/* eslint-enable */
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -9,12 +9,14 @@ export class CustomizedBridge extends Eip1193Bridge {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async sendAsync(...args: any) {
|
async sendAsync(...args: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('sendAsync called', ...args);
|
console.debug('sendAsync called', ...args);
|
||||||
return this.send(...args);
|
return this.send(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
override async send(...args: any) {
|
override async send(...args: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('send called', ...args);
|
console.debug('send called', ...args);
|
||||||
const isCallbackForm =
|
const isCallbackForm =
|
||||||
typeof args[0] === 'object' && typeof args[1] === 'function';
|
typeof args[0] === 'object' && typeof args[1] === 'function';
|
||||||
@ -89,6 +91,7 @@ export class CustomizedBridge extends Eip1193Bridge {
|
|||||||
// All other transactions the base class works for
|
// All other transactions the base class works for
|
||||||
result = await super.send(method, params);
|
result = await super.send(method, params);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('result received', method, params, result);
|
console.debug('result received', method, params, result);
|
||||||
if (isCallbackForm) {
|
if (isCallbackForm) {
|
||||||
callback(null, { result });
|
callback(null, { result });
|
||||||
@ -96,6 +99,7 @@ export class CustomizedBridge extends Eip1193Bridge {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
if (isCallbackForm) {
|
if (isCallbackForm) {
|
||||||
callback(error, null);
|
callback(error, null);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ActionsDropdown,
|
ActionsDropdown,
|
||||||
DropdownMenuCopyItem,
|
TradingDropdownCopyItem,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
@ -14,10 +14,13 @@ export const FillActionsDropdown = ({
|
|||||||
sellOrderId: string;
|
sellOrderId: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ActionsDropdown data-testid="market-actions-content">
|
<ActionsDropdown data-testid="fill-actions-content">
|
||||||
<DropdownMenuCopyItem value={tradeId} text={t('Copy trade ID')} />
|
<TradingDropdownCopyItem value={tradeId} text={t('Copy trade ID')} />
|
||||||
<DropdownMenuCopyItem value={buyOrderId} text={t('Copy buy order ID')} />
|
<TradingDropdownCopyItem
|
||||||
<DropdownMenuCopyItem
|
value={buyOrderId}
|
||||||
|
text={t('Copy buy order ID')}
|
||||||
|
/>
|
||||||
|
<TradingDropdownCopyItem
|
||||||
value={sellOrderId}
|
value={sellOrderId}
|
||||||
text={t('Copy sell order ID')}
|
text={t('Copy sell order ID')}
|
||||||
/>
|
/>
|
||||||
|
@ -48,6 +48,7 @@ describe('LocalLogger', () => {
|
|||||||
const consoleMethod = methodToConsoleMethod[i];
|
const consoleMethod = methodToConsoleMethod[i];
|
||||||
jest.spyOn(console, consoleMethod).mockImplementation();
|
jest.spyOn(console, consoleMethod).mockImplementation();
|
||||||
logger[method]('test', 'test2');
|
logger[method]('test', 'test2');
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
expect(console[consoleMethod]).toHaveBeenCalledWith(
|
expect(console[consoleMethod]).toHaveBeenCalledWith(
|
||||||
`trading:${methodToLevel[i]}: `,
|
`trading:${methodToLevel[i]}: `,
|
||||||
'test',
|
'test',
|
||||||
@ -100,10 +101,14 @@ describe('LocalLogger', () => {
|
|||||||
const logger = localLoggerFactory({ logLevel: 'info' });
|
const logger = localLoggerFactory({ logLevel: 'info' });
|
||||||
jest.spyOn(console, 'debug').mockImplementation();
|
jest.spyOn(console, 'debug').mockImplementation();
|
||||||
logger.debug('test', 'test1');
|
logger.debug('test', 'test1');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
expect(console.debug).not.toHaveBeenCalled();
|
expect(console.debug).not.toHaveBeenCalled();
|
||||||
|
|
||||||
logger.setLogLevel('debug');
|
logger.setLogLevel('debug');
|
||||||
logger.debug('test', 'test1');
|
logger.debug('test', 'test1');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
expect(console.debug).toHaveBeenCalledWith(
|
expect(console.debug).toHaveBeenCalledWith(
|
||||||
'trading:debug: ',
|
'trading:debug: ',
|
||||||
'test',
|
'test',
|
||||||
|
@ -99,6 +99,7 @@ export class LocalLogger {
|
|||||||
this.numberLogLevel <= LocalLogger.levelLogMap[level] // &&
|
this.numberLogLevel <= LocalLogger.levelLogMap[level] // &&
|
||||||
//!global.__LOGGER_SILENT_MODE__
|
//!global.__LOGGER_SILENT_MODE__
|
||||||
) {
|
) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console[logMethod].apply(console, [
|
console[logMethod].apply(console, [
|
||||||
`${this._application}:${level}: `,
|
`${this._application}:${level}: `,
|
||||||
...args,
|
...args,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
DropdownMenuItem,
|
TradingDropdownItem,
|
||||||
DropdownMenuCopyItem,
|
TradingDropdownCopyItem,
|
||||||
Link,
|
Link,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
@ -22,8 +22,8 @@ export const MarketActionsDropdown = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionsDropdown data-testid="market-actions-content">
|
<ActionsDropdown data-testid="market-actions-content">
|
||||||
<DropdownMenuCopyItem value={marketId} text={t('Copy Market ID')} />
|
<TradingDropdownCopyItem value={marketId} text={t('Copy Market ID')} />
|
||||||
<DropdownMenuItem>
|
<TradingDropdownItem>
|
||||||
<Link
|
<Link
|
||||||
href={linkCreator(EXPLORER_MARKET.replace(':id', marketId))}
|
href={linkCreator(EXPLORER_MARKET.replace(':id', marketId))}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -33,15 +33,15 @@ export const MarketActionsDropdown = ({
|
|||||||
{t('View on Explorer')}
|
{t('View on Explorer')}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</TradingDropdownItem>
|
||||||
<DropdownMenuItem
|
<TradingDropdownItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
open(assetId, e.target as HTMLElement);
|
open(assetId, e.target as HTMLElement);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||||
{t('View settlement asset details')}
|
{t('View settlement asset details')}
|
||||||
</DropdownMenuItem>
|
</TradingDropdownItem>
|
||||||
</ActionsDropdown>
|
</ActionsDropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -50,6 +50,7 @@ export const MarketsContainer = ({
|
|||||||
'tradableInstrument.instrument.code',
|
'tradableInstrument.instrument.code',
|
||||||
'tradableInstrument.instrument.product.settlementAsset',
|
'tradableInstrument.instrument.product.settlementAsset',
|
||||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||||
|
'market-actions',
|
||||||
].includes(colId)
|
].includes(colId)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
@ -83,7 +83,6 @@ export const marketProvider = makeDerivedDataProvider<
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const useMarket = (marketId?: string) => {
|
export const useMarket = (marketId?: string) => {
|
||||||
console.log(marketId);
|
|
||||||
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
|
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
|
||||||
return useDataProvider({
|
return useDataProvider({
|
||||||
dataProvider: marketProvider,
|
dataProvider: marketProvider,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ActionsDropdown,
|
ActionsDropdown,
|
||||||
DropdownMenuCopyItem,
|
TradingDropdownCopyItem,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export const OrderActionsDropdown = ({ id }: { id: string }) => {
|
export const OrderActionsDropdown = ({ id }: { id: string }) => {
|
||||||
return (
|
return (
|
||||||
<ActionsDropdown data-testid="market-actions-content">
|
<ActionsDropdown data-testid="order-actions-content">
|
||||||
<DropdownMenuCopyItem value={id} text={t('Copy order ID')} />
|
<TradingDropdownCopyItem value={id} text={t('Copy order ID')} />
|
||||||
</ActionsDropdown>
|
</ActionsDropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
ActionsDropdown,
|
ActionsDropdown,
|
||||||
DropdownMenuItem,
|
TradingDropdownItem,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@ -11,15 +11,15 @@ export const PositionActionsDropdown = ({ assetId }: { assetId: string }) => {
|
|||||||
const open = useAssetDetailsDialogStore((store) => store.open);
|
const open = useAssetDetailsDialogStore((store) => store.open);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionsDropdown data-testid="market-actions-content">
|
<ActionsDropdown data-testid="position-actions-content">
|
||||||
<DropdownMenuItem
|
<TradingDropdownItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
open(assetId, e.target as HTMLElement);
|
open(assetId, e.target as HTMLElement);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||||
{t('View settlement asset details')}
|
{t('View settlement asset details')}
|
||||||
</DropdownMenuItem>
|
</TradingDropdownItem>
|
||||||
</ActionsDropdown>
|
</ActionsDropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
DropdownMenuItem,
|
TradingDropdownItem,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
Link,
|
Link,
|
||||||
@ -12,8 +12,8 @@ export const ProposalActionsDropdown = ({ id }: { id: string }) => {
|
|||||||
const linkCreator = useLinks(DApp.Token);
|
const linkCreator = useLinks(DApp.Token);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionsDropdown data-testid="market-actions-content">
|
<ActionsDropdown data-testid="proposal-actions-content">
|
||||||
<DropdownMenuItem>
|
<TradingDropdownItem>
|
||||||
<Link
|
<Link
|
||||||
href={linkCreator(TOKEN_PROPOSAL.replace(':id', id))}
|
href={linkCreator(TOKEN_PROPOSAL.replace(':id', id))}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -21,7 +21,7 @@ export const ProposalActionsDropdown = ({ id }: { id: string }) => {
|
|||||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={16} />
|
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={16} />
|
||||||
{t('View proposal')}
|
{t('View proposal')}
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</TradingDropdownItem>
|
||||||
</ActionsDropdown>
|
</ActionsDropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useNextProtocolUpgradeProposal, useTimeToUpgrade } from '../lib';
|
import { useNextProtocolUpgradeProposal, useTimeToUpgrade } from '../lib';
|
||||||
import { convertToCountdownString } from '@vegaprotocol/utils';
|
import { convertToCountdownString } from '@vegaprotocol/utils';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon, NavigationContext } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
NavigationContext,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useProtocolUpgradeProposalLink } from '@vegaprotocol/environment';
|
import { useProtocolUpgradeProposalLink } from '@vegaprotocol/environment';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
export enum ProtocolUpgradeCountdownMode {
|
export enum ProtocolUpgradeCountdownMode {
|
||||||
IN_BLOCKS,
|
IN_BLOCKS,
|
||||||
IN_ESTIMATED_TIME_REMAINING,
|
IN_ESTIMATED_TIME_REMAINING,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProtocolUpgradeCountdownProps = {
|
type ProtocolUpgradeCountdownProps = {
|
||||||
mode?: ProtocolUpgradeCountdownMode;
|
mode?: ProtocolUpgradeCountdownMode;
|
||||||
};
|
};
|
||||||
export const ProtocolUpgradeCountdown = ({
|
export const ProtocolUpgradeCountdown = ({
|
||||||
mode = ProtocolUpgradeCountdownMode.IN_BLOCKS,
|
mode = ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING,
|
||||||
}: ProtocolUpgradeCountdownProps) => {
|
}: ProtocolUpgradeCountdownProps) => {
|
||||||
const { theme } = useContext(NavigationContext);
|
const { theme } = useContext(NavigationContext);
|
||||||
const { data, lastBlockHeight } = useNextProtocolUpgradeProposal();
|
const { data, lastBlockHeight } = useNextProtocolUpgradeProposal();
|
||||||
@ -75,20 +80,17 @@ export const ProtocolUpgradeCountdown = ({
|
|||||||
<div
|
<div
|
||||||
data-testid="protocol-upgrade-counter"
|
data-testid="protocol-upgrade-counter"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex flex-nowrap items-center text-xs py-2 px-4',
|
'flex flex-nowrap gap-1 items-center text-xs py-1 px-2 lg:px-4 h-8',
|
||||||
'border rounded',
|
'border rounded',
|
||||||
'border-vega-orange-500 dark:border-vega-orange-500',
|
'border-vega-orange-500 dark:border-vega-orange-500',
|
||||||
'bg-vega-orange-300 dark:bg-vega-orange-700',
|
'bg-vega-orange-300 dark:bg-vega-orange-700',
|
||||||
|
'text-default',
|
||||||
{
|
{
|
||||||
'!bg-transparent !border-black': theme === 'yellow',
|
'!bg-transparent !border-black': theme === 'yellow',
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />{' '}
|
||||||
name={IconNames.WARNING_SIGN}
|
|
||||||
size={3}
|
|
||||||
className={classNames('mr-2', emphasis)}
|
|
||||||
/>{' '}
|
|
||||||
<span className="flex gap-1 flex-nowrap whitespace-nowrap">
|
<span className="flex gap-1 flex-nowrap whitespace-nowrap">
|
||||||
<span>{t('Network upgrade in')} </span>
|
<span>{t('Network upgrade in')} </span>
|
||||||
{countdown}
|
{countdown}
|
||||||
|
@ -152,7 +152,7 @@ module.exports = {
|
|||||||
200: '#7C7E83',
|
200: '#7C7E83',
|
||||||
300: '#626469',
|
300: '#626469',
|
||||||
400: '#44464B',
|
400: '#44464B',
|
||||||
500: '#323339', // surface-container-highest
|
500: '#323339', // surface-container-highest, outline-surface-default
|
||||||
600: '#292B30',
|
600: '#292B30',
|
||||||
700: '#202227',
|
700: '#202227',
|
||||||
800: '#17191E', // surface-container
|
800: '#17191E', // surface-container
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { VegaIcon, VegaIconNames } from '../icon';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from './dropdown-menu';
|
|
||||||
|
|
||||||
export const ActionsDropdownTrigger = () => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
className='hover:bg-vega-light-200 dark:hover:bg-vega-dark-200 [&[aria-expanded="true"]]:bg-vega-light-200 dark:[&[aria-expanded="true"]]:bg-vega-dark-200 p-0.5 rounded-full'
|
|
||||||
data-testid="dropdown-menu"
|
|
||||||
>
|
|
||||||
<VegaIcon name={VegaIconNames.KEBAB} />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type ActionMenuContentProps = React.ComponentProps<typeof DropdownMenuContent>;
|
|
||||||
export const ActionsDropdown = (props: ActionMenuContentProps) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu trigger={<ActionsDropdownTrigger />}>
|
|
||||||
<DropdownMenuContent {...props}></DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
};
|
|
@ -26,7 +26,6 @@ export const CheckboxItems = () => {
|
|||||||
{ label: 'Moving average', state: useState(false) },
|
{ label: 'Moving average', state: useState(false) },
|
||||||
{ label: 'Price monitoring bands', state: useState(false) },
|
{ label: 'Price monitoring bands', state: useState(false) },
|
||||||
];
|
];
|
||||||
console.log(checkboxItems);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
@ -68,6 +67,7 @@ export const RadioItems = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
{/* eslint-disable no-console */}
|
||||||
<DropdownMenuItem onSelect={() => console.log('minimize')}>
|
<DropdownMenuItem onSelect={() => console.log('minimize')}>
|
||||||
Minimize window
|
Minimize window
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@ -77,6 +77,7 @@ export const RadioItems = () => {
|
|||||||
<DropdownMenuItem onSelect={() => console.log('smaller')}>
|
<DropdownMenuItem onSelect={() => console.log('smaller')}>
|
||||||
Smaller
|
Smaller
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
{/* eslint-enable */}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuRadioGroup value={selected} onValueChange={setSelected}>
|
<DropdownMenuRadioGroup value={selected} onValueChange={setSelected}>
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from './dropdown-menu';
|
export * from './dropdown-menu';
|
||||||
export * from './actions-dropdown';
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
export const IconExclaimationMark = ({ size = 16 }: { size: number }) => {
|
||||||
|
return (
|
||||||
|
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||||
|
<path d="M8 0.879997L7.57 1.63L0.130005 14.5H15.87L8 0.879997ZM8.75 12H7.25V10.5H8.75V12ZM7.25 9.5V6H8.75V9.5H7.25Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -11,6 +11,7 @@ import { IconCopy } from './svg-icons/icon-copy';
|
|||||||
import { IconCross } from './svg-icons/icon-cross';
|
import { IconCross } from './svg-icons/icon-cross';
|
||||||
import { IconDeposit } from './svg-icons/icon-deposit';
|
import { IconDeposit } from './svg-icons/icon-deposit';
|
||||||
import { IconEdit } from './svg-icons/icon-edit';
|
import { IconEdit } from './svg-icons/icon-edit';
|
||||||
|
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
|
||||||
import { IconForum } from './svg-icons/icon-forum';
|
import { IconForum } from './svg-icons/icon-forum';
|
||||||
import { IconGlobe } from './svg-icons/icon-globe';
|
import { IconGlobe } from './svg-icons/icon-globe';
|
||||||
import { IconInfo } from './svg-icons/icon-info';
|
import { IconInfo } from './svg-icons/icon-info';
|
||||||
@ -46,6 +47,7 @@ export enum VegaIconNames {
|
|||||||
CROSS = 'cross',
|
CROSS = 'cross',
|
||||||
DEPOSIT = 'deposit',
|
DEPOSIT = 'deposit',
|
||||||
EDIT = 'edit',
|
EDIT = 'edit',
|
||||||
|
EXCLAIMATION_MARK = 'exclaimation-mark',
|
||||||
FORUM = 'forum',
|
FORUM = 'forum',
|
||||||
GLOBE = 'globe',
|
GLOBE = 'globe',
|
||||||
INFO = 'info',
|
INFO = 'info',
|
||||||
@ -85,6 +87,7 @@ export const VegaIconNameMap: Record<
|
|||||||
cross: IconCross,
|
cross: IconCross,
|
||||||
deposit: IconDeposit,
|
deposit: IconDeposit,
|
||||||
edit: IconEdit,
|
edit: IconEdit,
|
||||||
|
'exclaimation-mark': IconExclaimationMark,
|
||||||
forum: IconForum,
|
forum: IconForum,
|
||||||
globe: IconGlobe,
|
globe: IconGlobe,
|
||||||
info: IconInfo,
|
info: IconInfo,
|
||||||
|
@ -16,7 +16,7 @@ export const VegaIcon = ({ size = 16, name }: VegaIconProps) => {
|
|||||||
);
|
);
|
||||||
const Element = VegaIconNameMap[name];
|
const Element = VegaIconNameMap[name];
|
||||||
return (
|
return (
|
||||||
<span className={effectiveClassName}>
|
<span className={effectiveClassName} data-testid={`icon-${name}`}>
|
||||||
<Element size={size} />
|
<Element size={size} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -49,6 +49,7 @@ export * from './toast';
|
|||||||
export * from './toggle';
|
export * from './toggle';
|
||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
export * from './trading-button';
|
export * from './trading-button';
|
||||||
|
export * from './trading-dropdown';
|
||||||
export * from './traffic-light';
|
export * from './traffic-light';
|
||||||
export * from './vega-icons';
|
export * from './vega-icons';
|
||||||
export * from './vega-logo';
|
export * from './vega-logo';
|
||||||
|
@ -19,6 +19,7 @@ export const RadioItems = () => {
|
|||||||
<span>Open</span>
|
<span>Open</span>
|
||||||
</NavDropdownMenuTrigger>
|
</NavDropdownMenuTrigger>
|
||||||
<NavDropdownMenuContent>
|
<NavDropdownMenuContent>
|
||||||
|
{/* eslint-disable no-console */}
|
||||||
<NavDropdownMenuItem onSelect={() => console.log('minimize')}>
|
<NavDropdownMenuItem onSelect={() => console.log('minimize')}>
|
||||||
Minimize window
|
Minimize window
|
||||||
</NavDropdownMenuItem>
|
</NavDropdownMenuItem>
|
||||||
@ -28,6 +29,7 @@ export const RadioItems = () => {
|
|||||||
<NavDropdownMenuItem onSelect={() => console.log('smaller')}>
|
<NavDropdownMenuItem onSelect={() => console.log('smaller')}>
|
||||||
Smaller
|
Smaller
|
||||||
</NavDropdownMenuItem>
|
</NavDropdownMenuItem>
|
||||||
|
{/* eslint-enable */}
|
||||||
</NavDropdownMenuContent>
|
</NavDropdownMenuContent>
|
||||||
</NavDropdownMenu>
|
</NavDropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +46,7 @@ RichDefaultSelect.args = {
|
|||||||
name: 'rich',
|
name: 'rich',
|
||||||
placeholder: 'Select an option',
|
placeholder: 'Select an option',
|
||||||
onValueChange: (v: string) => {
|
onValueChange: (v: string) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(v);
|
console.log(v);
|
||||||
},
|
},
|
||||||
children: (
|
children: (
|
||||||
|
@ -8,7 +8,7 @@ import type {
|
|||||||
import { Intent } from '../../utils/intent';
|
import { Intent } from '../../utils/intent';
|
||||||
|
|
||||||
type TradingButtonProps = {
|
type TradingButtonProps = {
|
||||||
size: 'large' | 'medium' | 'small';
|
size?: 'large' | 'medium' | 'small';
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@ -24,7 +24,7 @@ const getClassName = (
|
|||||||
className?: string
|
className?: string
|
||||||
) =>
|
) =>
|
||||||
classNames(
|
classNames(
|
||||||
'flex items-center justify-center rounded',
|
'flex gap-2 items-center justify-center rounded',
|
||||||
// size
|
// size
|
||||||
{
|
{
|
||||||
'h-12': !subLabel && size === 'large',
|
'h-12': !subLabel && size === 'large',
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { VegaIcon, VegaIconNames } from '../icon';
|
||||||
|
import {
|
||||||
|
TradingDropdown,
|
||||||
|
TradingDropdownContent,
|
||||||
|
TradingDropdownTrigger,
|
||||||
|
} from './trading-dropdown';
|
||||||
|
|
||||||
|
export const ActionsDropdownTrigger = () => {
|
||||||
|
return (
|
||||||
|
<TradingDropdownTrigger
|
||||||
|
className='hover:bg-vega-light-200 dark:hover:bg-vega-dark-200 [&[aria-expanded="true"]]:bg-vega-light-200 dark:[&[aria-expanded="true"]]:bg-vega-dark-200 p-0.5 rounded-full'
|
||||||
|
data-testid="dropdown-menu"
|
||||||
|
>
|
||||||
|
<button type="button">
|
||||||
|
<VegaIcon name={VegaIconNames.KEBAB} />
|
||||||
|
</button>
|
||||||
|
</TradingDropdownTrigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ActionMenuContentProps = React.ComponentProps<
|
||||||
|
typeof TradingDropdownContent
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ActionsDropdown = (props: ActionMenuContentProps) => {
|
||||||
|
return (
|
||||||
|
<TradingDropdown trigger={<ActionsDropdownTrigger />}>
|
||||||
|
<TradingDropdownContent {...props} side="bottom" align="end" />
|
||||||
|
</TradingDropdown>
|
||||||
|
);
|
||||||
|
};
|
2
libs/ui-toolkit/src/components/trading-dropdown/index.ts
Normal file
2
libs/ui-toolkit/src/components/trading-dropdown/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './trading-dropdown';
|
||||||
|
export * from './actions-dropdown';
|
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
TradingDropdown,
|
||||||
|
TradingDropdownContent,
|
||||||
|
TradingDropdownTrigger,
|
||||||
|
} from './trading-dropdown';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
describe('DropdownMenu', () => {
|
||||||
|
const text = 'Dropdown menu content';
|
||||||
|
|
||||||
|
// Upgrade from @radix-ui/react-dropdown-menu 0.1.6 to 2.0.2 renders
|
||||||
|
// dropdowns inline (rather than portals). Currently not using a portal
|
||||||
|
// will break the UI due to z-index issues
|
||||||
|
it('renders using a portal', async () => {
|
||||||
|
render(
|
||||||
|
<div className="test-wrapper">
|
||||||
|
<TradingDropdown
|
||||||
|
trigger={
|
||||||
|
<TradingDropdownTrigger>
|
||||||
|
<button>Trigger</button>
|
||||||
|
</TradingDropdownTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TradingDropdownContent>
|
||||||
|
<p>{text}</p>
|
||||||
|
</TradingDropdownContent>
|
||||||
|
</TradingDropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(screen.getByText(/trigger/i));
|
||||||
|
const contentElement = await screen.findByText(text);
|
||||||
|
expect(contentElement).toBeInTheDocument();
|
||||||
|
// if content is within .test-wrapper then its not been rendered in a portal
|
||||||
|
expect(contentElement.closest('.test-wrapper')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,209 @@
|
|||||||
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { ComponentProps, ReactNode } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { VegaIcon, VegaIconNames } from '../icon';
|
||||||
|
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||||
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
|
const itemClass = classNames(
|
||||||
|
'relative flex gap-2 items-center rounded-sm p-2 text-sm',
|
||||||
|
'cursor-default hover:cursor-pointer',
|
||||||
|
'hover:bg-vega-clight-400 dark:hover:bg-vega-cdark-400',
|
||||||
|
'focus:bg-vega-clight-400 dark:focus:bg-vega-cdark-400',
|
||||||
|
'select-none',
|
||||||
|
'whitespace-nowrap'
|
||||||
|
);
|
||||||
|
|
||||||
|
type TradingDropdownProps = DropdownMenuPrimitive.DropdownMenuProps & {
|
||||||
|
trigger: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all the parts of a dropdown menu.
|
||||||
|
*/
|
||||||
|
export const TradingDropdown = ({
|
||||||
|
children,
|
||||||
|
trigger,
|
||||||
|
...props
|
||||||
|
}: TradingDropdownProps) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Root {...props}>
|
||||||
|
{trigger}
|
||||||
|
<DropdownMenuPrimitive.Portal>{children}</DropdownMenuPrimitive.Portal>
|
||||||
|
</DropdownMenuPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The button that toggles the dropdown menu.
|
||||||
|
* By default, the {@link TradingDropdownContent} will position itself against the trigger.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownTrigger = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
||||||
|
DropdownMenuPrimitive.DropdownMenuTriggerProps
|
||||||
|
>(({ className, children, ...props }, forwardedRef) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Trigger
|
||||||
|
asChild={true}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.Trigger>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TradingDropdownTrigger.displayName = 'DropdownMenuTrigger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to group multiple {@link TradingDropdownRadioItem}s.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component that pops out when the dropdown menu is open.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownContent = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.Content>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, align = 'start', side, sideOffset = 10, ...contentProps },
|
||||||
|
forwardedRef
|
||||||
|
) => (
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={classNames(
|
||||||
|
'min-w-[290px] bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||||
|
'border border-vega-clight-500 dark:border-vega-cdark-500',
|
||||||
|
'p-2 rounded z-20 text-default'
|
||||||
|
)}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
side={side}
|
||||||
|
{...contentProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
TradingDropdownContent.displayName = 'DropdownMenuContent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component that contains the dropdown menu items.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownItem = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.Item>
|
||||||
|
>(({ className, ...itemProps }, forwardedRef) => (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
{...itemProps}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={classNames(itemClass, className)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TradingDropdownItem.displayName = 'DropdownMenuItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item that can be controlled and rendered like a checkbox.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownCheckboxItem = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, ...checkboxItemProps }, forwardedRef) => (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
{...checkboxItemProps}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={classNames(itemClass, 'justify-between', className)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TradingDropdownCheckboxItem.displayName = 'DropdownMenuCheckboxItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item that can be controlled and rendered like a radio.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownRadioItem = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
|
||||||
|
inset?: boolean;
|
||||||
|
}
|
||||||
|
>(({ className, inset = false, ...radioItemProps }, forwardedRef) => (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
{...radioItemProps}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={classNames(itemClass, 'justify-between', className)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TradingDropdownRadioItem.displayName = 'DropdownMenuRadioItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders when the parent {@link TradingDropdownCheckboxItem} or {@link TradingDropdownRadioItem} is checked.
|
||||||
|
* You can style this element directly, or you can use it as a wrapper to put an icon into, or both.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownItemIndicator = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.ItemIndicator>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
>(({ ...itemIndicatorProps }, forwardedRef) => (
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator
|
||||||
|
{...itemIndicatorProps}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className="flex-end text-vega-green"
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.TICK} />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
));
|
||||||
|
TradingDropdownItemIndicator.displayName = 'DropdownMenuItemIndicator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to visually separate items in the dropdown menu.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownSeparator = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
|
React.ComponentProps<typeof DropdownMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...separatorProps }, forwardedRef) => (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
{...separatorProps}
|
||||||
|
ref={forwardedRef}
|
||||||
|
className={classNames(
|
||||||
|
'h-px my-1 mx-2 bg-vega-clight-500 dark:bg-vega-cdark-500',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TradingDropdownSeparator.displayName = 'DropdownMenuSeparator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Portal to ensure menu portions are rendered outwith where they appear in the
|
||||||
|
* DOM.
|
||||||
|
*/
|
||||||
|
export const TradingDropdownPortal = (
|
||||||
|
portalProps: ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||||
|
) => <DropdownMenuPrimitive.Portal {...portalProps} />;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a regular DropdownMenuItem with copy to clip board functionality
|
||||||
|
*/
|
||||||
|
export const TradingDropdownCopyItem = ({
|
||||||
|
value,
|
||||||
|
text,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
text: string;
|
||||||
|
}) => {
|
||||||
|
const [copied, setCopied] = useCopyTimeout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CopyToClipboard text={value} onCopy={() => setCopied(true)}>
|
||||||
|
<TradingDropdownItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.COPY} size={16} />
|
||||||
|
{text}
|
||||||
|
{copied && (
|
||||||
|
<span className="text-xs text-neutral-500">{t('Copied')}</span>
|
||||||
|
)}
|
||||||
|
</TradingDropdownItem>
|
||||||
|
</CopyToClipboard>
|
||||||
|
);
|
||||||
|
};
|
@ -4,12 +4,14 @@ import { ethers } from 'ethers';
|
|||||||
export class Eip1193CustomBridge extends Eip1193Bridge {
|
export class Eip1193CustomBridge extends Eip1193Bridge {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async sendAsync(...args: any) {
|
async sendAsync(...args: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('sendAsync called', ...args);
|
console.debug('sendAsync called', ...args);
|
||||||
return this.send(...args);
|
return this.send(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
override async send(...args: any) {
|
override async send(...args: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('send called', ...args);
|
console.debug('send called', ...args);
|
||||||
const isCallbackForm =
|
const isCallbackForm =
|
||||||
typeof args[0] === 'object' && typeof args[1] === 'function';
|
typeof args[0] === 'object' && typeof args[1] === 'function';
|
||||||
@ -68,6 +70,7 @@ export class Eip1193CustomBridge extends Eip1193Bridge {
|
|||||||
// All other transactions the base class works for
|
// All other transactions the base class works for
|
||||||
result = await super.send(method, params);
|
result = await super.send(method, params);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.debug('result received', method, params, result);
|
console.debug('result received', method, params, result);
|
||||||
if (isCallbackForm) {
|
if (isCallbackForm) {
|
||||||
callback(null, { result });
|
callback(null, { result });
|
||||||
|
@ -89,7 +89,7 @@ const ConnectButton = ({
|
|||||||
setEagerConnector(info.name);
|
setEagerConnector(info.name);
|
||||||
onClick?.();
|
onClick?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('could not connect to the wallet', info.name, err);
|
console.warn('could not connect to the wallet', info.name, err);
|
||||||
// NOOP - cancelled wallet connector
|
// NOOP - cancelled wallet connector
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -26,7 +26,7 @@ export const initializeCoinbaseConnector = (providerUrl: string) =>
|
|||||||
url: providerUrl,
|
url: providerUrl,
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log('ERR_COINBASE_WALLET', error);
|
console.warn('ERR_COINBASE_WALLET', error);
|
||||||
useWeb3ConnectStore.setState({ error });
|
useWeb3ConnectStore.setState({ error });
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -82,7 +82,7 @@ export const initializeWalletConnector = (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log('ERR_WALLET_CONNECT', error.message);
|
console.warn('ERR_WALLET_CONNECT', error.message);
|
||||||
useWeb3ConnectStore.setState({ error });
|
useWeb3ConnectStore.setState({ error });
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -98,7 +98,7 @@ export const initializeMetaMaskConnector = () =>
|
|||||||
mustBeMetaMask: false,
|
mustBeMetaMask: false,
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log('ERR_META_MASK', error.message);
|
console.warn('ERR_META_MASK', error.message);
|
||||||
useWeb3ConnectStore.setState({ error });
|
useWeb3ConnectStore.setState({ error });
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,7 @@ import { t } from '@vegaprotocol/i18n';
|
|||||||
import {
|
import {
|
||||||
ActionsDropdown,
|
ActionsDropdown,
|
||||||
ButtonLink,
|
ButtonLink,
|
||||||
DropdownMenuItem,
|
TradingDropdownItem,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@ -175,7 +175,7 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
|||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
|
|
||||||
<ActionsDropdown>
|
<ActionsDropdown>
|
||||||
<DropdownMenuItem
|
<TradingDropdownItem
|
||||||
key={'withdrawal-approval'}
|
key={'withdrawal-approval'}
|
||||||
data-testid="withdrawal-approval"
|
data-testid="withdrawal-approval"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -186,7 +186,7 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
|||||||
>
|
>
|
||||||
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
|
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
|
||||||
{t('View withdrawal details')}
|
{t('View withdrawal details')}
|
||||||
</DropdownMenuItem>
|
</TradingDropdownItem>
|
||||||
</ActionsDropdown>
|
</ActionsDropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user