feat: mobile navbar on Console (#2547)
* feat: mobile navbar on Console * feat: mobile navbar on Console - adjust unit test * feat: mobile navbar on Console - adjust unit test * feat: mobile navbar on Console - adjust themes * feat: mobile navbar on Console - add some unit tests * feat: mobile navbar on Console - refactor solution * feat: mobile navbar on Console - adjust int tests * feat: mobile navbar on Console - adjust styling * feat: mobile navbar on Console - move close button into the drawer * feat: mobile navbar on Console - adjust int tests * chore: close drawe after navigation * chore: mobile navbar on Console - adjust unit tests * chore: mobile navbar on Console - adjust unit tests Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
ff53e5e841
commit
45a4dd7009
@ -142,19 +142,48 @@ describe('Navbar', { tags: '@smoke' }, () => {
|
|||||||
const hashes = ['#/markets/all', '#/markets/market-0', '#/portfolio'];
|
const hashes = ['#/markets/all', '#/markets/market-0', '#/portfolio'];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
cy.getByTestId('navbar').within(() => {
|
cy.getByTestId('navbar').within(() => {
|
||||||
cy.get('a[data-testid]', { log: true })
|
cy.get('[data-testid="navbar-links"] a[data-testid]', { log: true })
|
||||||
.should('have.length', 3)
|
.should('have.length', 3)
|
||||||
.each((item) => {
|
.each((item) => {
|
||||||
cy.wrap(item).click();
|
cy.wrap(item).click();
|
||||||
cy.wrap(item).get('span.absolute.h-1.w-full').should('exist');
|
cy.wrap(item).get('span.absolute.md\\:h-1.w-full').should('exist');
|
||||||
cy.location('hash').should('equal', hashes[i]);
|
cy.location('hash').should('equal', hashes[i]);
|
||||||
cy.wrap(item).should('have.data', 'testid', links[i++]);
|
cy.wrap(item).should('have.data', 'testid', links[i++]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should look nicer on mobile', () => {
|
it('wallet drawer should be correctly rendered', () => {
|
||||||
cy.viewport(560, 890);
|
cy.viewport(560, 890);
|
||||||
cy.getByTestId('theme-switcher').scrollIntoView().click();
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('menu drawer should be correctly rendered', () => {
|
||||||
|
cy.viewport(560, 890);
|
||||||
|
cy.getByTestId('button-menu-drawer').click();
|
||||||
|
cy.getByTestId('menu-drawer').should('be.visible');
|
||||||
|
cy.getByTestId('menu-drawer').within((el) => {
|
||||||
|
cy.wrap(el).getByTestId('Markets').click();
|
||||||
|
cy.location('hash').should('equal', '#/markets/all');
|
||||||
|
});
|
||||||
|
cy.getByTestId('button-menu-drawer').click();
|
||||||
|
cy.getByTestId('menu-drawer').within((el) => {
|
||||||
|
cy.wrap(el).getByTestId('Trading').click();
|
||||||
|
cy.location('hash').should('equal', '#/markets/market-0');
|
||||||
|
});
|
||||||
|
cy.getByTestId('button-menu-drawer').click();
|
||||||
|
cy.getByTestId('menu-drawer').within((el) => {
|
||||||
|
cy.wrap(el).getByTestId('Portfolio').click();
|
||||||
|
cy.location('hash').should('equal', '#/portfolio');
|
||||||
|
cy.wrap(el).getByTestId('theme-switcher').should('be.visible');
|
||||||
|
});
|
||||||
|
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -227,9 +227,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
|
|
||||||
describe('redirect should take last visited market into consideration', () => {
|
describe('redirect should take last visited market into consideration', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window().then((window) => {
|
cy.clearLocalStorage();
|
||||||
window.localStorage.removeItem('marketId');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('marketId comes from existing market', () => {
|
it('marketId comes from existing market', () => {
|
||||||
cy.window().then((window) => {
|
cy.window().then((window) => {
|
||||||
@ -237,7 +235,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.wait('@Market');
|
cy.wait('@Market');
|
||||||
cy.location('hash').should('equal', '#/markets/market-1');
|
cy.location('hash').should('equal', '#/markets/market-1');
|
||||||
cy.get('[role="dialog"]').should('not.exist');
|
cy.getByTestId('dialog-content').should('not.exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -250,7 +248,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.wait('@Market');
|
cy.wait('@Market');
|
||||||
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
||||||
cy.get('[role="dialog"]').should('not.exist');
|
cy.getByTestId('dialog-content').should('not.exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,17 +2,19 @@ import * as Schema from '@vegaprotocol/types';
|
|||||||
|
|
||||||
describe('markets table', { tags: '@smoke' }, () => {
|
describe('markets table', { tags: '@smoke' }, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage(
|
cy.clearLocalStorage().then(() => {
|
||||||
Schema.MarketState.STATE_ACTIVE,
|
cy.mockTradingPage(
|
||||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
|
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
);
|
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
|
||||||
cy.mockSubscription();
|
);
|
||||||
cy.visit('/');
|
cy.mockSubscription();
|
||||||
cy.wait('@Market');
|
cy.visit('/');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Market');
|
||||||
cy.wait('@MarketsData');
|
cy.wait('@Markets');
|
||||||
cy.wait('@MarketsCandles');
|
cy.wait('@MarketsData');
|
||||||
|
cy.wait('@MarketsCandles');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders markets correctly', () => {
|
it('renders markets correctly', () => {
|
||||||
|
@ -22,6 +22,7 @@ export const Home = () => {
|
|||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
} else if (data) {
|
} else if (data) {
|
||||||
|
update({ shouldDisplayWelcomeDialog: true });
|
||||||
const marketDataId = data[0]?.id;
|
const marketDataId = data[0]?.id;
|
||||||
if (marketDataId) {
|
if (marketDataId) {
|
||||||
navigate(Links[Routes.MARKET](marketDataId), {
|
navigate(Links[Routes.MARKET](marketDataId), {
|
||||||
@ -30,7 +31,6 @@ export const Home = () => {
|
|||||||
} else {
|
} else {
|
||||||
navigate(Links[Routes.MARKET]());
|
navigate(Links[Routes.MARKET]());
|
||||||
}
|
}
|
||||||
update({ shouldDisplayWelcomeDialog: true });
|
|
||||||
}
|
}
|
||||||
}, [marketId, data, navigate, update]);
|
}, [marketId, data, navigate, update]);
|
||||||
|
|
||||||
|
18
apps/trading/components/icons/wallet.tsx
Normal file
18
apps/trading/components/icons/wallet.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const WalletIcon = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="26"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 26 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
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"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { NavLink, Link } from 'react-router-dom';
|
import { NavLink, Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
@ -9,7 +10,7 @@ import {
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { useGlobalStore } from '../../stores/global';
|
import { useGlobalStore } from '../../stores/global';
|
||||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||||
import { NewTab, ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
import { Drawer, NewTab, ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Vega } from '../icons/vega';
|
import { Vega } from '../icons/vega';
|
||||||
import type { HTMLAttributeAnchorTarget } from 'react';
|
import type { HTMLAttributeAnchorTarget } from 'react';
|
||||||
import { Links, Routes } from '../../pages/client-router';
|
import { Links, Routes } from '../../pages/client-router';
|
||||||
@ -24,7 +25,17 @@ interface NavbarProps {
|
|||||||
navbarTheme?: NavbarTheme;
|
navbarTheme?: NavbarTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
const LinkList = ({
|
||||||
|
navbarTheme,
|
||||||
|
className = 'flex',
|
||||||
|
dataTestId = 'navbar-links',
|
||||||
|
onNavigate,
|
||||||
|
}: {
|
||||||
|
navbarTheme: NavbarTheme;
|
||||||
|
className?: string;
|
||||||
|
dataTestId?: string;
|
||||||
|
onNavigate?: () => void;
|
||||||
|
}) => {
|
||||||
const tokenLink = useLinks(DApp.Token);
|
const tokenLink = useLinks(DApp.Token);
|
||||||
const { marketId } = useGlobalStore((store) => ({
|
const { marketId } = useGlobalStore((store) => ({
|
||||||
marketId: store.marketId,
|
marketId: store.marketId,
|
||||||
@ -33,47 +44,130 @@ export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
|||||||
? Links[Routes.MARKET](marketId)
|
? Links[Routes.MARKET](marketId)
|
||||||
: Links[Routes.MARKET]();
|
: Links[Routes.MARKET]();
|
||||||
return (
|
return (
|
||||||
<Nav
|
<div className={className} data-testid={dataTestId}>
|
||||||
navbarTheme={navbarTheme}
|
|
||||||
title={t('Console')}
|
|
||||||
titleContent={<NetworkSwitcher />}
|
|
||||||
icon={
|
|
||||||
<Link to="/">
|
|
||||||
<Vega className="w-13" />
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AppNavLink
|
<AppNavLink
|
||||||
name={t('Markets')}
|
name={t('Markets')}
|
||||||
path={Links[Routes.MARKETS]()}
|
path={Links[Routes.MARKETS]()}
|
||||||
navbarTheme={navbarTheme}
|
navbarTheme={navbarTheme}
|
||||||
|
onClick={onNavigate}
|
||||||
end
|
end
|
||||||
/>
|
/>
|
||||||
<AppNavLink
|
<AppNavLink
|
||||||
name={t('Trading')}
|
name={t('Trading')}
|
||||||
path={tradingPath}
|
path={tradingPath}
|
||||||
navbarTheme={navbarTheme}
|
navbarTheme={navbarTheme}
|
||||||
|
onClick={onNavigate}
|
||||||
end
|
end
|
||||||
/>
|
/>
|
||||||
<AppNavLink
|
<AppNavLink
|
||||||
name={t('Portfolio')}
|
name={t('Portfolio')}
|
||||||
path={Links[Routes.PORTFOLIO]()}
|
path={Links[Routes.PORTFOLIO]()}
|
||||||
navbarTheme={navbarTheme}
|
navbarTheme={navbarTheme}
|
||||||
|
onClick={onNavigate}
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
href={tokenLink(TOKEN_GOVERNANCE)}
|
href={tokenLink(TOKEN_GOVERNANCE)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className={getActiveNavLinkClassNames(false, navbarTheme)}
|
className={classNames(
|
||||||
|
'w-full md:w-auto',
|
||||||
|
getActiveNavLinkClassNames(false, navbarTheme)
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center justify-between w-full gap-2 pr-3 md:pr-0">
|
||||||
{t('Governance')}
|
{t('Governance')}
|
||||||
<NewTab />
|
<NewTab />
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MobileMenuBar = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const menuButton = (
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col justify-around gap-3 p-2 relative z-30 h-[34px]',
|
||||||
|
{
|
||||||
|
'z-50': drawerOpen,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onClick={() => setDrawerOpen(!drawerOpen)}
|
||||||
|
data-testid="button-menu-drawer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames('w-[26px] h-[2px] transition-all', {
|
||||||
|
'translate-y-0 rotate-0 bg-white': !drawerOpen,
|
||||||
|
'bg-black': !drawerOpen && navbarTheme === 'yellow',
|
||||||
|
'translate-y-[7.5px] rotate-45 bg-black dark:bg-white': drawerOpen,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classNames('w-[26px] h-[2px] transition-all', {
|
||||||
|
'translate-y-0 rotate-0 bg-white': !drawerOpen,
|
||||||
|
'bg-black': !drawerOpen && navbarTheme === 'yellow',
|
||||||
|
'-translate-y-[7.5px] -rotate-45 bg-black dark:bg-white': drawerOpen,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex overflow-hidden md:hidden" ref={setContainer}>
|
||||||
|
<Drawer
|
||||||
|
dataTestId="menu-drawer"
|
||||||
|
open={drawerOpen}
|
||||||
|
onChange={setDrawerOpen}
|
||||||
|
container={container}
|
||||||
|
trigger={menuButton}
|
||||||
|
>
|
||||||
|
<div className="border-l border-default px-4 py-2 gap-4 flex flex-col w-full h-full bg-white dark:bg-black dark:text-white justify-start">
|
||||||
|
<div className="w-full h-1"></div>
|
||||||
|
<div className="px-2 pt-10 w-full flex flex-col items-stretch">
|
||||||
|
<NetworkSwitcher />
|
||||||
|
<div className="w-full pt-8 h-1 border-b border-default"></div>
|
||||||
|
</div>
|
||||||
|
<LinkList
|
||||||
|
className="flex flex-col"
|
||||||
|
navbarTheme={navbarTheme}
|
||||||
|
dataTestId="mobile-navbar-links"
|
||||||
|
onNavigate={() => setDrawerOpen(false)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col px-2 justify-between">
|
||||||
|
<div className="w-full h-1 border-t border-default py-5"></div>
|
||||||
|
<ThemeSwitcher withMobile />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||||
|
const titleContent = (
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<NetworkSwitcher />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
navbarTheme={navbarTheme}
|
||||||
|
title={t('Console')}
|
||||||
|
titleContent={titleContent}
|
||||||
|
icon={
|
||||||
|
<Link to="/">
|
||||||
|
<Vega className="w-13" />
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<LinkList className="hidden md:flex" navbarTheme={navbarTheme} />
|
||||||
|
<div className="flex items-center gap-2 ml-auto overflow-hidden">
|
||||||
<VegaWalletConnectButton />
|
<VegaWalletConnectButton />
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher className="hidden md:block" />
|
||||||
|
<MobileMenuBar navbarTheme={navbarTheme} />
|
||||||
</div>
|
</div>
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
@ -86,6 +180,7 @@ interface AppNavLinkProps {
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
target?: HTMLAttributeAnchorTarget;
|
target?: HTMLAttributeAnchorTarget;
|
||||||
end?: boolean;
|
end?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppNavLink = ({
|
const AppNavLink = ({
|
||||||
@ -95,16 +190,21 @@ const AppNavLink = ({
|
|||||||
target,
|
target,
|
||||||
testId = name,
|
testId = name,
|
||||||
end,
|
end,
|
||||||
|
onClick,
|
||||||
}: AppNavLinkProps) => {
|
}: AppNavLinkProps) => {
|
||||||
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
|
const borderClasses = classNames(
|
||||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
'absolute h-[2px] md:h-1 w-full bottom-[-1px] left-0',
|
||||||
'bg-black': navbarTheme === 'yellow',
|
{
|
||||||
});
|
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||||
|
'bg-black dark:bg-vega-yellow md:dark:bg-black': navbarTheme === 'yellow',
|
||||||
|
}
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
to={{ pathname: path }}
|
to={{ pathname: path }}
|
||||||
className={getNavLinkClassNames(navbarTheme)}
|
className={getNavLinkClassNames(navbarTheme)}
|
||||||
|
onClick={onClick}
|
||||||
target={target}
|
target={target}
|
||||||
end={end}
|
end={end}
|
||||||
>
|
>
|
||||||
|
@ -7,9 +7,7 @@ import { truncateByChars } from '@vegaprotocol/react-helpers';
|
|||||||
const mockUpdateDialogOpen = jest.fn();
|
const mockUpdateDialogOpen = jest.fn();
|
||||||
jest.mock('@vegaprotocol/wallet', () => ({
|
jest.mock('@vegaprotocol/wallet', () => ({
|
||||||
...jest.requireActual('@vegaprotocol/wallet'),
|
...jest.requireActual('@vegaprotocol/wallet'),
|
||||||
useVegaWalletDialogStore: () => ({
|
useVegaWalletDialogStore: () => mockUpdateDialogOpen,
|
||||||
openVegaWalletDialog: mockUpdateDialogOpen,
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -27,7 +25,7 @@ const generateJsx = (context: VegaWalletContextShape) => {
|
|||||||
it('Not connected', () => {
|
it('Not connected', () => {
|
||||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||||
|
|
||||||
const button = screen.getByRole('button');
|
const button = screen.getByTestId('connect-vega-wallet');
|
||||||
expect(button).toHaveTextContent('Connect Vega wallet');
|
expect(button).toHaveTextContent('Connect Vega wallet');
|
||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||||
@ -42,7 +40,7 @@ it('Connected', () => {
|
|||||||
} as VegaWalletContextShape)
|
} as VegaWalletContextShape)
|
||||||
);
|
);
|
||||||
|
|
||||||
const button = screen.getByRole('button');
|
const button = screen.getByTestId('manage-vega-wallet');
|
||||||
expect(button).toHaveTextContent(truncateByChars(pubKey.publicKey));
|
expect(button).toHaveTextContent(truncateByChars(pubKey.publicKey));
|
||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
expect(mockUpdateDialogOpen).not.toHaveBeenCalled();
|
expect(mockUpdateDialogOpen).not.toHaveBeenCalled();
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { t, truncateByChars } from '@vegaprotocol/react-helpers';
|
import { t, truncateByChars } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -9,17 +12,125 @@ import {
|
|||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Icon,
|
Icon,
|
||||||
|
Drawer,
|
||||||
} 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 { useEffect, useMemo, useState } from 'react';
|
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import { WalletIcon } from '../icons/wallet';
|
||||||
|
|
||||||
|
const MobileWalletButton = ({
|
||||||
|
isConnected,
|
||||||
|
activeKey,
|
||||||
|
}: {
|
||||||
|
isConnected?: boolean;
|
||||||
|
activeKey?: PubKey;
|
||||||
|
}) => {
|
||||||
|
const { pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
setDrawerOpen(!drawerOpen);
|
||||||
|
}
|
||||||
|
}, [drawerOpen, 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="md: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="m-4">
|
||||||
|
<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);
|
||||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
(store) => store.openVegaWalletDialog
|
||||||
}));
|
);
|
||||||
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||||
const isConnected = pubKey !== null;
|
const isConnected = pubKey !== null;
|
||||||
|
|
||||||
@ -29,44 +140,55 @@ export const VegaWalletConnectButton = () => {
|
|||||||
|
|
||||||
if (isConnected && pubKeys) {
|
if (isConnected && pubKeys) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={dropdownOpen}>
|
<>
|
||||||
<DropdownMenuTrigger
|
<div className="hidden md:block">
|
||||||
data-testid="manage-vega-wallet"
|
<DropdownMenu open={dropdownOpen}>
|
||||||
onClick={() => setDropdownOpen((curr) => !curr)}
|
<DropdownMenuTrigger
|
||||||
>
|
data-testid="manage-vega-wallet"
|
||||||
{activeKey && <span className="uppercase">{activeKey.name}</span>}
|
onClick={() => setDropdownOpen((curr) => !curr)}
|
||||||
{': '}
|
|
||||||
{truncateByChars(pubKey)}
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent onInteractOutside={() => setDropdownOpen(false)}>
|
|
||||||
<div className="min-w-[340px]" data-testid="keypair-list">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={pubKey}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
selectPubKey(value);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{pubKeys.map((pk) => (
|
{activeKey && <span className="uppercase">{activeKey.name}</span>}
|
||||||
<KeypairItem key={pk.publicKey} pk={pk} />
|
{': '}
|
||||||
))}
|
{truncateByChars(pubKey)}
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
|
<DropdownMenuContent
|
||||||
{t('Disconnect')}
|
onInteractOutside={() => setDropdownOpen(false)}
|
||||||
</DropdownMenuItem>
|
>
|
||||||
</div>
|
<div className="min-w-[340px]" data-testid="keypair-list">
|
||||||
</DropdownMenuContent>
|
<DropdownMenuRadioGroup
|
||||||
</DropdownMenu>
|
value={pubKey}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
selectPubKey(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pubKeys.map((pk) => (
|
||||||
|
<KeypairItem key={pk.publicKey} pk={pk} />
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
|
||||||
|
{t('Disconnect')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<MobileWalletButton isConnected activeKey={activeKey} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<>
|
||||||
data-testid="connect-vega-wallet"
|
<Button
|
||||||
onClick={openVegaWalletDialog}
|
data-testid="connect-vega-wallet"
|
||||||
size="sm"
|
onClick={openVegaWalletDialog}
|
||||||
>
|
size="sm"
|
||||||
<span className="whitespace-nowrap">{t('Connect Vega wallet')}</span>
|
className="hidden md:block"
|
||||||
</Button>
|
>
|
||||||
|
<span className="whitespace-nowrap">{t('Connect Vega wallet')}</span>
|
||||||
|
</Button>
|
||||||
|
<MobileWalletButton />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -115,3 +237,58 @@ const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
|||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const KeypairListItem = ({
|
||||||
|
pk,
|
||||||
|
isActive,
|
||||||
|
onSelectItem,
|
||||||
|
}: {
|
||||||
|
pk: PubKey;
|
||||||
|
isActive: boolean;
|
||||||
|
onSelectItem: (pk: string) => void;
|
||||||
|
}) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
let timeout: any;
|
||||||
|
|
||||||
|
if (copied) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [copied]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||||
|
data-testid={`key-${pk.publicKey}-mobile`}
|
||||||
|
>
|
||||||
|
<span className="mr-2">
|
||||||
|
<button onClick={() => onSelectItem(pk.publicKey)}>
|
||||||
|
<span className="uppercase">{pk.name}</span>
|
||||||
|
</button>
|
||||||
|
{isActive && <Icon name="tick" className="ml-2" />}
|
||||||
|
</span>
|
||||||
|
<span className="text-neutral-500 dark:text-neutral-400">
|
||||||
|
{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>
|
||||||
|
<Icon name="duplicate" className="mr-2" />
|
||||||
|
</button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
{copied && (
|
||||||
|
<span className="text-xs text-neutral-500">{t('Copied')}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -5,7 +5,7 @@ declare global {
|
|||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
interface Chainable<Subject> {
|
interface Chainable<Subject> {
|
||||||
connectVegaWallet(): void;
|
connectVegaWallet(isMobile?: boolean): void;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
interface Chainable<Subject> {
|
interface Chainable<Subject> {
|
||||||
@ -25,10 +25,12 @@ export const mockConnectWallet = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function addVegaWalletConnect() {
|
export function addVegaWalletConnect() {
|
||||||
Cypress.Commands.add('connectVegaWallet', () => {
|
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
|
||||||
mockConnectWallet();
|
mockConnectWallet();
|
||||||
cy.highlight(`Connecting Vega Wallet`);
|
cy.highlight(`Connecting Vega Wallet`);
|
||||||
cy.get('[data-testid=connect-vega-wallet]').click();
|
cy.get(
|
||||||
|
`[data-testid=connect-vega-wallet${isMobile ? '-mobile' : ''}]`
|
||||||
|
).click();
|
||||||
cy.get('[data-testid=connectors-list]')
|
cy.get('[data-testid=connectors-list]')
|
||||||
.find('[data-testid="connector-jsonRpc"]')
|
.find('[data-testid="connector-jsonRpc"]')
|
||||||
.click();
|
.click();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback, useRef } from 'react';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
@ -93,11 +93,20 @@ export const NetworkSwitcher = () => {
|
|||||||
},
|
},
|
||||||
[setOpen, setAdvancedView]
|
[setOpen, setAdvancedView]
|
||||||
);
|
);
|
||||||
|
const menuRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={isOpen} onOpenChange={handleOpen}>
|
<DropdownMenu open={isOpen} onOpenChange={handleOpen}>
|
||||||
<DropdownMenuTrigger>{envTriggerMapping[VEGA_ENV]}</DropdownMenuTrigger>
|
<DropdownMenuTrigger
|
||||||
<DropdownMenuContent align="start">
|
ref={menuRef}
|
||||||
|
className="w-full flex justify-between items-center"
|
||||||
|
>
|
||||||
|
{envTriggerMapping[VEGA_ENV]}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="start"
|
||||||
|
style={{ minWidth: `${menuRef.current?.offsetWidth || 290}px` }}
|
||||||
|
>
|
||||||
{!isAdvancedView && (
|
{!isAdvancedView && (
|
||||||
<>
|
<>
|
||||||
{standardNetworkKeys.map((key) => (
|
{standardNetworkKeys.map((key) => (
|
||||||
@ -121,6 +130,7 @@ export const NetworkSwitcher = () => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setAdvancedView(true);
|
setAdvancedView(true);
|
||||||
}}
|
}}
|
||||||
|
className="w-full flex flex-col items-stretch"
|
||||||
>
|
>
|
||||||
{t('Advanced')}
|
{t('Advanced')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@ -137,7 +147,10 @@ export const NetworkSwitcher = () => {
|
|||||||
isAvailable={!!VEGA_NETWORKS[key]}
|
isAvailable={!!VEGA_NETWORKS[key]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span data-testid="network-item-description">
|
<span
|
||||||
|
className="hidden md:inline"
|
||||||
|
data-testid="network-item-description"
|
||||||
|
>
|
||||||
{envDescriptionMapping[key]}
|
{envDescriptionMapping[key]}
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
@ -16,6 +16,7 @@ interface DialogProps {
|
|||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
size?: 'small' | 'medium';
|
size?: 'small' | 'medium';
|
||||||
|
dataTestId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({
|
||||||
@ -27,6 +28,7 @@ export function Dialog({
|
|||||||
icon,
|
icon,
|
||||||
intent,
|
intent,
|
||||||
size = 'small',
|
size = 'small',
|
||||||
|
dataTestId = 'dialog-content',
|
||||||
}: DialogProps) {
|
}: DialogProps) {
|
||||||
const contentClasses = classNames(
|
const contentClasses = classNames(
|
||||||
'fixed top-0 left-0 z-20 flex justify-center items-start overflow-auto',
|
'fixed top-0 left-0 z-20 flex justify-center items-start overflow-auto',
|
||||||
@ -64,6 +66,7 @@ export function Dialog({
|
|||||||
<DialogPrimitives.Content
|
<DialogPrimitives.Content
|
||||||
className={contentClasses}
|
className={contentClasses}
|
||||||
onCloseAutoFocus={onCloseAutoFocus}
|
onCloseAutoFocus={onCloseAutoFocus}
|
||||||
|
data-testid={dataTestId}
|
||||||
>
|
>
|
||||||
<div className={wrapperClasses}>
|
<div className={wrapperClasses}>
|
||||||
{onChange && (
|
{onChange && (
|
||||||
|
38
libs/ui-toolkit/src/components/drawer/drawer.stories.tsx
Normal file
38
libs/ui-toolkit/src/components/drawer/drawer.stories.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { Drawer } from './drawer';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from '../button';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Drawer',
|
||||||
|
component: Drawer,
|
||||||
|
} as ComponentMeta<typeof Drawer>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof Drawer> = (args) => {
|
||||||
|
const [open, setOpen] = useState(args.open);
|
||||||
|
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||||
|
const openButton = <Button onClick={() => setOpen(true)}>Open drawer</Button>;
|
||||||
|
return (
|
||||||
|
<div ref={setContainer}>
|
||||||
|
<Drawer
|
||||||
|
{...args}
|
||||||
|
open={open}
|
||||||
|
onChange={setOpen}
|
||||||
|
container={container}
|
||||||
|
trigger={openButton}
|
||||||
|
>
|
||||||
|
{args.children}
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
open: false,
|
||||||
|
children: (
|
||||||
|
<p className="h-full bg-black dark:bg-white text-white dark:text-black">
|
||||||
|
Some content
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
};
|
74
libs/ui-toolkit/src/components/drawer/drawer.tsx
Normal file
74
libs/ui-toolkit/src/components/drawer/drawer.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
|
interface DrawerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
open: boolean;
|
||||||
|
onChange?: (isOpen: boolean) => void;
|
||||||
|
onCloseAutoFocus?: (e: Event) => void;
|
||||||
|
container?: HTMLElement | null;
|
||||||
|
dataTestId?: string;
|
||||||
|
className?: string;
|
||||||
|
showClose?: boolean;
|
||||||
|
trigger?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Drawer({
|
||||||
|
children,
|
||||||
|
open,
|
||||||
|
onChange,
|
||||||
|
onCloseAutoFocus,
|
||||||
|
dataTestId = 'drawer-content',
|
||||||
|
container,
|
||||||
|
className = '',
|
||||||
|
showClose,
|
||||||
|
trigger,
|
||||||
|
}: DrawerProps) {
|
||||||
|
const contentClasses = classNames(
|
||||||
|
'h-full max-w-[500px] w-[90vw] z-10 top-0 right-0 fixed transition-transform',
|
||||||
|
className,
|
||||||
|
{
|
||||||
|
'translate-x-[100%]': !open,
|
||||||
|
'translate-x-0 z-40': open,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayClass = classNames('inset-0 bg-black/50 z-20', {
|
||||||
|
hidden: !open,
|
||||||
|
fixed: open,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogPrimitives.Root
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(x) => onChange?.(x)}
|
||||||
|
modal={false}
|
||||||
|
>
|
||||||
|
<DialogPrimitives.Trigger asChild>{trigger}</DialogPrimitives.Trigger>
|
||||||
|
<DialogPrimitives.Portal forceMount container={container}>
|
||||||
|
<DialogPrimitives.Overlay
|
||||||
|
className={overlayClass}
|
||||||
|
data-testid="dialog-overlay"
|
||||||
|
/>
|
||||||
|
<DialogPrimitives.Content
|
||||||
|
className={contentClasses}
|
||||||
|
onCloseAutoFocus={onCloseAutoFocus}
|
||||||
|
data-testid={dataTestId}
|
||||||
|
forceMount
|
||||||
|
>
|
||||||
|
{showClose && onChange && (
|
||||||
|
<DialogPrimitives.Close
|
||||||
|
className="absolute p-2 top-0 right-0 md:top-2 md:right-2"
|
||||||
|
data-testid="drawer-close"
|
||||||
|
>
|
||||||
|
<Icon name="cross" />
|
||||||
|
</DialogPrimitives.Close>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</DialogPrimitives.Content>
|
||||||
|
</DialogPrimitives.Portal>
|
||||||
|
</DialogPrimitives.Root>
|
||||||
|
);
|
||||||
|
}
|
1
libs/ui-toolkit/src/components/drawer/index.ts
Normal file
1
libs/ui-toolkit/src/components/drawer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './drawer';
|
@ -8,6 +8,7 @@ export * from './callout';
|
|||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
export * from './copy-with-tooltip';
|
export * from './copy-with-tooltip';
|
||||||
export * from './dialog';
|
export * from './dialog';
|
||||||
|
export * from './drawer';
|
||||||
export * from './dropdown-menu';
|
export * from './dropdown-menu';
|
||||||
export * from './form-group';
|
export * from './form-group';
|
||||||
export * from './icon';
|
export * from './icon';
|
||||||
|
@ -15,13 +15,15 @@ export const getActiveNavLinkClassNames = (
|
|||||||
navbarTheme: string,
|
navbarTheme: string,
|
||||||
fullWidth = false
|
fullWidth = false
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
return classNames('mx-2 py-3 self-end relative', {
|
return classNames('mx-2 my-4 md:my-0 md:py-3 self-start relative', {
|
||||||
'cursor-default': isActive,
|
'cursor-default': isActive,
|
||||||
'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
|
'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
|
||||||
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
|
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
|
||||||
!isActive && navbarTheme !== 'yellow',
|
!isActive && navbarTheme !== 'yellow',
|
||||||
'text-black': isActive && navbarTheme === 'yellow',
|
'text-black dark:text-white md:dark:text-black':
|
||||||
'text-black/60 hover:text-black': !isActive && navbarTheme === 'yellow',
|
isActive && navbarTheme === 'yellow',
|
||||||
|
'text-black/60 dark:text-neutral-400 md:dark:text-black/60 hover:text-black':
|
||||||
|
!isActive && navbarTheme === 'yellow',
|
||||||
'flex-1': fullWidth,
|
'flex-1': fullWidth,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
import { useThemeSwitcher, t } from '@vegaprotocol/react-helpers';
|
||||||
import { SunIcon, MoonIcon } from './icons';
|
import { SunIcon, MoonIcon } from './icons';
|
||||||
|
import { Toggle } from '../toggle';
|
||||||
|
|
||||||
export const ThemeSwitcher = ({ className }: { className?: string }) => {
|
export const ThemeSwitcher = ({
|
||||||
|
className,
|
||||||
|
withMobile,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
withMobile?: boolean;
|
||||||
|
}) => {
|
||||||
const { theme, setTheme } = useThemeSwitcher();
|
const { theme, setTheme } = useThemeSwitcher();
|
||||||
return (
|
const button = (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTheme()}
|
onClick={() => setTheme()}
|
||||||
@ -14,4 +21,31 @@ export const ThemeSwitcher = ({ className }: { className?: string }) => {
|
|||||||
{theme === 'light' && <MoonIcon />}
|
{theme === 'light' && <MoonIcon />}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
const toggles = [
|
||||||
|
{
|
||||||
|
value: 'dark',
|
||||||
|
label: t('Dark mode'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'light',
|
||||||
|
label: t('Light mode'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return withMobile ? (
|
||||||
|
<>
|
||||||
|
<div className="flex grow gap-6 md:hidden whitespace-nowrap justify-between">
|
||||||
|
{button}{' '}
|
||||||
|
<Toggle
|
||||||
|
name="theme-switch"
|
||||||
|
checkedValue={theme}
|
||||||
|
toggles={toggles}
|
||||||
|
onChange={() => setTheme()}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block">{button}</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
button
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ export interface ToggleProps {
|
|||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
checkedValue?: string | undefined | null;
|
checkedValue?: string | undefined | null;
|
||||||
type?: 'primary' | 'buy' | 'sell';
|
type?: 'primary' | 'buy' | 'sell';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Toggle = ({
|
export const Toggle = ({
|
||||||
@ -23,6 +24,7 @@ export const Toggle = ({
|
|||||||
onChange,
|
onChange,
|
||||||
checkedValue,
|
checkedValue,
|
||||||
type = 'primary',
|
type = 'primary',
|
||||||
|
size = 'lg',
|
||||||
...props
|
...props
|
||||||
}: ToggleProps) => {
|
}: ToggleProps) => {
|
||||||
const fieldsetClasses =
|
const fieldsetClasses =
|
||||||
@ -35,7 +37,6 @@ export const Toggle = ({
|
|||||||
const buttonClasses = classnames(
|
const buttonClasses = classnames(
|
||||||
'relative inline-block w-full text-center',
|
'relative inline-block w-full text-center',
|
||||||
'peer-checked:rounded-full',
|
'peer-checked:rounded-full',
|
||||||
'px-10 py-2',
|
|
||||||
{
|
{
|
||||||
'peer-checked:bg-neutral-400 dark:peer-checked:bg-white dark:peer-checked:text-black':
|
'peer-checked:bg-neutral-400 dark:peer-checked:bg-white dark:peer-checked:text-black':
|
||||||
type === 'primary',
|
type === 'primary',
|
||||||
@ -45,7 +46,12 @@ export const Toggle = ({
|
|||||||
type === 'sell',
|
type === 'sell',
|
||||||
},
|
},
|
||||||
'peer-checked:text-white dark:peer-checked:text-black',
|
'peer-checked:text-white dark:peer-checked:text-black',
|
||||||
'cursor-pointer peer-checked:cursor-auto select-none'
|
'cursor-pointer peer-checked:cursor-auto select-none',
|
||||||
|
{
|
||||||
|
'px-10 py-2': size === 'lg',
|
||||||
|
'px-8 py-2': size === 'md',
|
||||||
|
'px-6 py-2': size === 'sm',
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user