vega-frontend-monorepo/apps/trading/components/navbar/navbar.tsx

441 lines
13 KiB
TypeScript
Raw Normal View History

2023-07-31 16:08:55 +00:00
import type { ButtonHTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
import { useState } from 'react';
import {
useEnvironment,
DocsLinks,
Networks,
DApp,
useLinks,
FLAGS,
useEnvNameMapping,
} from '@vegaprotocol/environment';
import { useGlobalStore } from '../../stores';
feat(#927) design update (#1201) * feat: create new buttons * feat: update anchor and button link styles * feat: add icon support * feat: fix full width with icon * feat: convert invalid button props to use new props * feat: tidy up explorer * feat: more tidy up for token and trading * feat: move styles to css file using @apply * chore: remove css with @apply as its not working in apps * fix: deposit form button * feat: use default tailwind config, start on forms * feat: fixup trade grid styles * feat: form styles * feat: styles for order book and tables * feat: make key management use dropdown * feat: update various components * feat: tidy up wallet section * feat: token tidy up * feat: token governance styles * Feat/927: Dialog styling * feat: token styles * feat: add font familys * feat: change token borders to be softer * feat: console-lite changes to support new theme * Feat/927: Centered key-value-table.tsx spacing * Feat/927: Tweak to Explorer site border colours to be inline with trading * Feat/927: Tweak to Explorer header * Feat/927: Theme switcher icon colours * Feat/927: Fix for Explorer block data styling * feat: fix tests, add status footer and change logos * feat: render both theme icons to avoid hydration error * chore: run migrations for project * fix: tailwindconfig build to work with new next version * feat: use document page for next as per documentation * chore: update build targets to use development mode when serving * fix: console-lite default text colors * chore: fix tooltip text break, change submit button * feat: adjust console-lite styles to work with tabs * feat: add bespoke dialog for console-lite market-selector * Feat/927: Theme switcher now has prop for fixed bg colour * Feat/927: Font size and border radius tweak for toggles * Feat/927: Cleaned up trade-grid.tsx spacing * feat: responsive styles for market header and nav * feat: update designs for market popover * fix: nav active state * chore: allow classname to be passed to button * Feat/927: Fix Token width on desktop (was overflowing) * Feat/927: Fix token header h1 from wrapping * Feat/927: Tweak for claim-flow.tsx * fix: connect button test * Feat/927: Proposals list styling polish * Feat/927: key-value-table.tsx spacing tweak * feat: add copy button to kp dropdown * Feat/927: Removing old theme params and uses * Feat/927: Removing old theme params and uses, documenting the now used otb sizes * feat: use key val table in asset dialog * feat: align tooltip styles * fix: orderbook grid alignment * chore: linting * fix: dialog sizing in medium mode, node switcher styles * chore: remove unused color classes * feat: update radio and checkbox designs * feat: updates to storybook * feat: update design system stories * chore: stories update * chore: rename resize panels and tidy * feat: fix checkbox tick * fix: add poyfills for jest in trading test setup * chore: fix checkbox tests * chore: fix tests * chore: fix tests again * chore: revert token wallet name test * fix: tooltip tests on console-lite * fix: wallet dropdown test Co-authored-by: sam-keen <samuel.kleinmann@gmail.com>
2022-08-31 04:35:46 +00:00
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import {
VegaIconNames,
VegaIcon,
VLogo,
LanguageSelector,
ThemeSwitcher,
} from '@vegaprotocol/ui-toolkit';
2023-07-31 16:08:55 +00:00
import * as N from '@radix-ui/react-navigation-menu';
import * as D from '@radix-ui/react-dialog';
import { NavLink } from 'react-router-dom';
import { Links } from '../../lib/links';
2023-07-31 16:08:55 +00:00
import classNames from 'classnames';
import { VegaWalletMenu } from '../vega-wallet';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { WalletIcon } from '../icons/wallet';
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
import { useT, useI18n } from '../../lib/use-t';
import { supportedLngs } from '../../lib/i18n';
2023-07-31 16:08:55 +00:00
type MenuState = 'wallet' | 'nav' | null;
type Theme = 'system' | 'yellow';
feat(#927) design update (#1201) * feat: create new buttons * feat: update anchor and button link styles * feat: add icon support * feat: fix full width with icon * feat: convert invalid button props to use new props * feat: tidy up explorer * feat: more tidy up for token and trading * feat: move styles to css file using @apply * chore: remove css with @apply as its not working in apps * fix: deposit form button * feat: use default tailwind config, start on forms * feat: fixup trade grid styles * feat: form styles * feat: styles for order book and tables * feat: make key management use dropdown * feat: update various components * feat: tidy up wallet section * feat: token tidy up * feat: token governance styles * Feat/927: Dialog styling * feat: token styles * feat: add font familys * feat: change token borders to be softer * feat: console-lite changes to support new theme * Feat/927: Centered key-value-table.tsx spacing * Feat/927: Tweak to Explorer site border colours to be inline with trading * Feat/927: Tweak to Explorer header * Feat/927: Theme switcher icon colours * Feat/927: Fix for Explorer block data styling * feat: fix tests, add status footer and change logos * feat: render both theme icons to avoid hydration error * chore: run migrations for project * fix: tailwindconfig build to work with new next version * feat: use document page for next as per documentation * chore: update build targets to use development mode when serving * fix: console-lite default text colors * chore: fix tooltip text break, change submit button * feat: adjust console-lite styles to work with tabs * feat: add bespoke dialog for console-lite market-selector * Feat/927: Theme switcher now has prop for fixed bg colour * Feat/927: Font size and border radius tweak for toggles * Feat/927: Cleaned up trade-grid.tsx spacing * feat: responsive styles for market header and nav * feat: update designs for market popover * fix: nav active state * chore: allow classname to be passed to button * Feat/927: Fix Token width on desktop (was overflowing) * Feat/927: Fix token header h1 from wrapping * Feat/927: Tweak for claim-flow.tsx * fix: connect button test * Feat/927: Proposals list styling polish * Feat/927: key-value-table.tsx spacing tweak * feat: add copy button to kp dropdown * Feat/927: Removing old theme params and uses * Feat/927: Removing old theme params and uses, documenting the now used otb sizes * feat: use key val table in asset dialog * feat: align tooltip styles * fix: orderbook grid alignment * chore: linting * fix: dialog sizing in medium mode, node switcher styles * chore: remove unused color classes * feat: update radio and checkbox designs * feat: updates to storybook * feat: update design system stories * chore: stories update * chore: rename resize panels and tidy * feat: fix checkbox tick * fix: add poyfills for jest in trading test setup * chore: fix checkbox tests * chore: fix tests * chore: fix tests again * chore: revert token wallet name test * fix: tooltip tests on console-lite * fix: wallet dropdown test Co-authored-by: sam-keen <samuel.kleinmann@gmail.com>
2022-08-31 04:35:46 +00:00
2023-03-10 15:46:51 +00:00
export const Navbar = ({
2023-07-31 16:08:55 +00:00
children,
2023-03-10 15:46:51 +00:00
theme = 'system',
}: {
2023-07-31 16:08:55 +00:00
children?: ReactNode;
theme?: Theme;
}) => {
const i18n = useI18n();
const t = useT();
2023-07-31 16:08:55 +00:00
// menu state for small screens
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',
})}
style={{
background: theme === 'yellow' ? 'url(/testnet-logo-bg.png' : 'none',
}}
2023-07-31 16:08:55 +00:00
>
<VLogo className="w-4" />
</NavLink>
{/* Left section */}
<div className="flex items-center lg:hidden">{children}</div>
2023-07-31 16:08:55 +00:00
{/* Used to show header in nav on mobile */}
<div className="hidden lg:block">
<NavbarMenu onClick={() => setMenu(null)} />
</div>
{/* Right section */}
2023-11-15 21:46:19 +00:00
<div className="ml-auto flex items-center justify-end gap-2">
2023-07-31 16:08:55 +00:00
<ProtocolUpgradeCountdown />
<div className="flex">
<ThemeSwitcher />
{supportedLngs.length > 1 ? (
<LanguageSelector
languages={supportedLngs}
onSelect={(language) => i18n.changeLanguage(language)}
/>
) : null}
</div>
2023-07-31 16:08:55 +00:00
<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
2023-11-15 21:46:19 +00:00
className="fixed inset-0 z-20 bg-black/50 dark:bg-black/80 lg:hidden"
2023-07-31 16:08:55 +00:00
data-testid="navbar-menu-overlay"
/>
<D.Content
className={classNames(
'lg:hidden',
2023-11-15 21:46:19 +00:00
'border-default bg-vega-clight-700 dark:bg-vega-cdark-700 fixed right-0 top-0 z-20 h-screen w-3/4 border-l',
2023-07-31 16:08:55 +00:00
navTextClasses
)}
data-testid="navbar-menu-content"
>
2023-11-15 21:46:19 +00:00
<div className="flex h-10 items-center justify-end p-1">
2023-07-31 16:08:55 +00:00
<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 t = useT();
const envNameMapping = useEnvNameMapping();
2023-07-31 16:08:55 +00:00
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
const marketId = useGlobalStore((store) => store.marketId);
return (
2023-11-15 21:46:19 +00:00
<div className="gap-3 lg:flex lg:h-full">
2023-07-31 16:08:55 +00:00
<NavbarList>
<NavbarItem>
<NavbarTrigger data-testid="navbar-network-switcher-trigger">
{envNameMapping[VEGA_ENV]}
</NavbarTrigger>
<NavbarContent data-testid="navbar-content-network-switcher">
<ul className="lg:p-4">
{[Networks.MAINNET, Networks.TESTNET].map((n) => {
const url = VEGA_NETWORKS[n];
if (!url) return;
return (
<NavbarSubItem key={n}>
<NavbarLink to={url}>{envNameMapping[n]}</NavbarLink>
</NavbarSubItem>
);
})}
</ul>
</NavbarContent>
</NavbarItem>
</NavbarList>
<NavbarListDivider />
<NavbarList>
<NavbarItem>
<NavbarLink to={Links.MARKETS()} onClick={onClick}>
2023-03-10 15:46:51 +00:00
{t('Markets')}
2023-07-31 16:08:55 +00:00
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links.MARKET(marketId || '')} onClick={onClick}>
2023-03-10 15:46:51 +00:00
{t('Trading')}
2023-07-31 16:08:55 +00:00
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links.PORTFOLIO()} onClick={onClick}>
2023-03-10 15:46:51 +00:00
{t('Portfolio')}
2023-07-31 16:08:55 +00:00
</NavbarLink>
</NavbarItem>
{FLAGS.REFERRALS && (
<NavbarItem>
<NavbarLink end={false} to={Links.REFERRALS()} onClick={onClick}>
{t('Referrals')}
</NavbarLink>
</NavbarItem>
)}
2023-10-25 21:59:30 +00:00
<NavbarItem>
<NavbarLink to={Links.FEES()} onClick={onClick}>
{t('Fees')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links.REWARDS()} onClick={onClick}>
{t('Rewards')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLinkExternal to={useLinks(DApp.Governance)()}>
{t('Governance')}
</NavbarLinkExternal>
</NavbarItem>
2023-07-31 16:08:55 +00:00
<NavbarItem>
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
<NavbarContent data-testid="navbar-content-resources">
<ul className="lg:p-4">
{DocsLinks?.NEW_TO_VEGA && (
<NavbarSubItem>
<NavbarLinkExternal to={DocsLinks?.NEW_TO_VEGA}>
{t('Docs')}
2023-07-31 16:08:55 +00:00
</NavbarLinkExternal>
</NavbarSubItem>
)}
{GITHUB_FEEDBACK_URL && (
<NavbarSubItem>
<NavbarLinkExternal to={GITHUB_FEEDBACK_URL}>
{t('Give Feedback')}
2023-07-31 16:08:55 +00:00
</NavbarLinkExternal>
</NavbarSubItem>
)}
<NavbarSubItem>
<NavbarLink to={Links.DISCLAIMER()} onClick={onClick}>
2023-07-31 16:08:55 +00:00
{t('Disclaimer')}
</NavbarLink>
</NavbarSubItem>
</ul>
</NavbarContent>
</NavbarItem>
</NavbarList>
</div>
);
};
/**
* Wrapper for radix-ux Trigger for consistent styles
*/
const NavbarTrigger = ({
children,
...props
}: N.NavigationMenuTriggerProps) => {
return (
<N.Trigger
{...props}
onPointerMove={preventHover}
onPointerLeave={preventHover}
className={classNames(
2023-11-15 21:46:19 +00:00
'w-full lg:h-full lg:w-auto',
'flex items-center justify-between gap-2 px-6 py-2 lg:justify-center lg:p-0',
2023-07-31 16:08:55 +00:00
'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,
end = true,
2023-07-31 16:08:55 +00:00
}: {
children: ReactNode;
to: string;
onClick?: () => void;
end?: boolean;
2023-07-31 16:08:55 +00:00
}) => {
return (
<N.Link asChild={true}>
<NavLink
to={to}
end={end}
2023-07-31 16:08:55 +00:00
className={classNames(
2023-11-15 21:46:19 +00:00
'block flex-col justify-center lg:flex lg:h-full',
'px-6 py-2 text-lg lg:p-0 lg:text-sm',
2023-07-31 16:08:55 +00:00
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
)}
2023-07-31 16:08:55 +00:00
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(
2023-11-15 21:46:19 +00:00
'absolute bottom-0 left-0 hidden h-0 w-full lg:block',
2023-07-31 16:08:55 +00:00
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) => {
2023-11-15 21:46:19 +00:00
return <N.List {...props} className="gap-6 lg:flex lg:h-full" />;
2023-07-31 16:08:55 +00:00
};
/**
* Content that gets rendered when a sub section of the navbar is shown
*/
const NavbarContent = (props: N.NavigationMenuContentProps) => {
return (
<N.Content
{...props}
className={classNames(
2023-11-15 21:46:19 +00:00
'navbar-content group',
'z-20 pl-2 lg:absolute lg:mt-2 lg:min-w-[290px] lg:pl-0',
2023-07-31 16:08:55 +00:00
'lg:bg-vega-clight-700 lg:dark:bg-vega-cdark-700',
2023-11-15 21:46:19 +00:00
'border-vega-clight-500 dark:border-vega-cdark-500 lg:rounded lg:border'
2023-07-31 16:08:55 +00:00
)}
onPointerEnter={preventHover}
onPointerLeave={preventHover}
/>
);
};
2023-07-31 16:08:55 +00:00
/**
* NavbarLink with OPEN_EXTERNAL icon
*/
const NavbarLinkExternal = ({
children,
2023-07-31 16:08:55 +00:00
to,
onClick,
}: {
children: ReactNode;
2023-07-31 16:08:55 +00:00
to: string;
onClick?: () => void;
}) => {
return (
2023-07-31 16:08:55 +00:00
<N.Link asChild={true}>
<NavLink
to={to}
className={classNames(
2023-11-15 21:46:19 +00:00
'flex items-center gap-2 lg:h-full',
'px-6 py-2 text-lg lg:p-0 lg:text-sm',
2023-07-31 16:08:55 +00:00
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
)}
onClick={onClick}
target="_blank"
>
<span>{children}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={14} />
2023-07-31 16:08:55 +00:00
</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="px-6 py-2 lg:px-0" role="separator">
2023-11-15 21:46:19 +00:00
<div className="bg-vega-clight-500 dark:bg-vega-cdark-500 h-px w-full lg:h-full lg:w-px" />
2023-07-31 16:08:55 +00:00
</div>
);
};
2023-07-31 16:08:55 +00:00
/**
* Button component to avoid repeating styles for buttons shown on small screens
*/
const NavbarMobileButton = (props: ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
{...props}
className={classNames(
2023-11-15 21:46:19 +00:00
'flex h-8 w-8 items-center rounded p-1 lg:hidden ',
2023-07-31 16:08:55 +00:00
'hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500',
'hover:text-vega-clight-50 dark:hover:text-vega-cdark-50'
)}
/>
);
};
// https://github.com/radix-ui/primitives/issues/1630
// eslint-disable-next-line
const preventHover = (e: any) => {
e.preventDefault();
};