-
- {error?.message && (
-
-
- {error.message}
-
-
+ const searchForm = (
+
);
+
+ return (
+ <>
+
+ {searchForm}
+
+
+
+
+
+
+
+ {searchForm}
+
+
+
+ >
+ );
};
diff --git a/apps/explorer/src/app/routes/router-config.tsx b/apps/explorer/src/app/routes/router-config.tsx
index 772b60c64..f8a5b7e5d 100644
--- a/apps/explorer/src/app/routes/router-config.tsx
+++ b/apps/explorer/src/app/routes/router-config.tsx
@@ -23,14 +23,18 @@ import { NetworkParameters } from './network-parameters';
import type { RouteObject } from 'react-router-dom';
import { MarketPage, MarketsPage } from './markets';
-export type Navigable = { path: string; name: string; text: string };
+export type Navigable = {
+ path: string;
+ name: string;
+ text: string;
+};
type Route = RouteObject & Navigable;
const partiesRoutes: Route[] = flags.parties
? [
{
path: Routes.PARTIES,
- name: 'Parties',
+ name: t('Parties'),
text: t('Parties'),
element:
,
children: [
@@ -52,7 +56,7 @@ const assetsRoutes: Route[] = flags.assets
{
path: Routes.ASSETS,
text: t('Assets'),
- name: 'Assets',
+ name: t('Assets'),
children: [
{
index: true,
@@ -71,7 +75,7 @@ const genesisRoutes: Route[] = flags.genesis
? [
{
path: Routes.GENESIS,
- name: 'Genesis',
+ name: t('Genesis'),
text: t('Genesis Parameters'),
element:
,
},
@@ -82,7 +86,7 @@ const governanceRoutes: Route[] = flags.governance
? [
{
path: Routes.GOVERNANCE,
- name: 'Governance proposals',
+ name: t('Governance proposals'),
text: t('Governance Proposals'),
element:
,
},
@@ -93,7 +97,7 @@ const marketsRoutes: Route[] = flags.markets
? [
{
path: Routes.MARKETS,
- name: 'Markets',
+ name: t('Markets'),
text: t('Markets'),
children: [
{
@@ -113,17 +117,18 @@ const networkParametersRoutes: Route[] = flags.networkParameters
? [
{
path: Routes.NETWORK_PARAMETERS,
- name: 'NetworkParameters',
+ name: t('NetworkParameters'),
text: t('Network Parameters'),
element:
,
},
]
: [];
+
const validators: Route[] = flags.validators
? [
{
path: Routes.VALIDATORS,
- name: 'Validators',
+ name: t('Validators'),
text: t('Validators'),
element:
,
},
@@ -133,14 +138,14 @@ const validators: Route[] = flags.validators
const routerConfig: Route[] = [
{
path: Routes.HOME,
- name: 'Home',
+ name: t('Home'),
text: t('Home'),
element:
,
index: true,
},
{
path: Routes.TX,
- name: 'Txs',
+ name: t('Txs'),
text: t('Transactions'),
element:
,
children: [
@@ -160,7 +165,7 @@ const routerConfig: Route[] = [
},
{
path: Routes.BLOCKS,
- name: 'Blocks',
+ name: t('Blocks'),
text: t('Blocks'),
element:
,
children: [
@@ -176,7 +181,7 @@ const routerConfig: Route[] = [
},
{
path: Routes.ORACLES,
- name: 'Oracles',
+ name: t('Oracles'),
text: t('Oracles'),
element:
,
children: [
diff --git a/apps/explorer/src/app/setup-tests.ts b/apps/explorer/src/app/setup-tests.ts
index 8f2609b7b..25ebbd3be 100644
--- a/apps/explorer/src/app/setup-tests.ts
+++ b/apps/explorer/src/app/setup-tests.ts
@@ -3,3 +3,13 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+
+Object.defineProperty(window, 'ResizeObserver', {
+ writable: false,
+ value: jest.fn().mockImplementation(() => ({
+ observe: jest.fn(),
+ unobserve: jest.fn(),
+ connect: jest.fn(),
+ disconnect: jest.fn(),
+ })),
+});
diff --git a/libs/environment/src/components/network-switcher/network-switcher.tsx b/libs/environment/src/components/network-switcher/network-switcher.tsx
index 8effd6489..b3b51fbe6 100644
--- a/libs/environment/src/components/network-switcher/network-switcher.tsx
+++ b/libs/environment/src/components/network-switcher/network-switcher.tsx
@@ -80,7 +80,13 @@ const NetworkLabel = ({
);
-export const NetworkSwitcher = () => {
+type NetworkSwitcherProps = {
+ /**
+ * The current network identifier, defaults to the `VEGA_ENV` if unset.
+ */
+ currentNetwork?: Networks;
+};
+export const NetworkSwitcher = ({ currentNetwork }: NetworkSwitcherProps) => {
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const tokenLink = useLinks(DApp.Token);
const [isOpen, setOpen] = useState(false);
@@ -97,6 +103,8 @@ export const NetworkSwitcher = () => {
);
const menuRef = useRef
(null);
+ const current = currentNetwork || VEGA_ENV;
+
return (
{
ref={menuRef}
className="flex justify-between items-center"
>
- {envTriggerMapping[VEGA_ENV]}
+ {envTriggerMapping[current]}
}
>
@@ -125,7 +133,7 @@ export const NetworkSwitcher = () => {
{envNameMapping[key]}
@@ -150,7 +158,7 @@ export const NetworkSwitcher = () => {
{envNameMapping[key]}
diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js
index 245a551f1..307c4f2f3 100644
--- a/libs/tailwindcss-config/src/theme.js
+++ b/libs/tailwindcss-config/src/theme.js
@@ -165,5 +165,6 @@ module.exports = {
},
data: {
selected: 'state~="checked"',
+ open: 'state~="open"',
},
};
diff --git a/libs/ui-toolkit/src/components/drawer/drawer.stories.tsx b/libs/ui-toolkit/src/components/drawer/drawer.stories.tsx
index 70fd8c12e..b691f8629 100644
--- a/libs/ui-toolkit/src/components/drawer/drawer.stories.tsx
+++ b/libs/ui-toolkit/src/components/drawer/drawer.stories.tsx
@@ -31,7 +31,7 @@ export const Default = Template.bind({});
Default.args = {
open: false,
children: (
-
+
Some content
),
diff --git a/libs/ui-toolkit/src/components/drawer/drawer.tsx b/libs/ui-toolkit/src/components/drawer/drawer.tsx
index 57176197e..d294e677a 100644
--- a/libs/ui-toolkit/src/components/drawer/drawer.tsx
+++ b/libs/ui-toolkit/src/components/drawer/drawer.tsx
@@ -1,7 +1,9 @@
import type { ReactNode } from 'react';
+import { createContext } from 'react';
import classNames from 'classnames';
import * as DialogPrimitives from '@radix-ui/react-dialog';
import { Icon } from '../icon';
+import { create } from 'zustand';
interface DrawerProps {
children: ReactNode;
@@ -15,6 +17,19 @@ interface DrawerProps {
trigger?: ReactNode;
}
+export const DrawerContext = createContext(undefined);
+
+type DrawerStore = {
+ drawerOpen: boolean;
+ setDrawerOpen: (isOpen: boolean) => void;
+};
+export const useDrawer = create((set) => ({
+ drawerOpen: false,
+ setDrawerOpen: (isOpen) => {
+ set({ drawerOpen: isOpen });
+ },
+}));
+
export function Drawer({
children,
open,
@@ -27,11 +42,12 @@ export function Drawer({
trigger,
}: DrawerProps) {
const contentClasses = classNames(
+ 'group/drawer',
'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,
+ 'translate-x-0 z-20': open,
}
);
diff --git a/libs/ui-toolkit/src/components/dropdown-menu/dropdown-menu.tsx b/libs/ui-toolkit/src/components/dropdown-menu/dropdown-menu.tsx
index 88ff553a9..13379bac5 100644
--- a/libs/ui-toolkit/src/components/dropdown-menu/dropdown-menu.tsx
+++ b/libs/ui-toolkit/src/components/dropdown-menu/dropdown-menu.tsx
@@ -73,7 +73,7 @@ export const DropdownMenuContent = forwardRef<
diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts
index abfd9d66d..4cb56fa2b 100644
--- a/libs/ui-toolkit/src/components/index.ts
+++ b/libs/ui-toolkit/src/components/index.ts
@@ -1,8 +1,8 @@
export * from './accordion';
+export * from './announcement-banner';
export * from './arrows';
export * from './async-renderer';
export * from './background-video';
-export * from './announcement-banner';
export * from './button';
export * from './callout';
export * from './checkbox';
@@ -19,12 +19,16 @@ export * from './key-value-table';
export * from './link';
export * from './loader';
export * from './lozenge';
-export * from './nav';
+export * from './maintenance-page';
export * from './nav-dropdown';
+export * from './nav';
+export * from './navigation';
+export * from './notification';
export * from './popover';
export * from './progress-bar';
export * from './radio-group';
export * from './resizable-grid';
+export * from './rounded-wrapper';
export * from './select';
export * from './simple-grid';
export * from './slider';
@@ -35,13 +39,10 @@ export * from './tabs';
export * from './text-area';
export * from './theme-switcher';
export * from './thumbs';
+export * from './toast';
export * from './toggle';
export * from './tooltip';
+export * from './traffic-light';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
-export * from './traffic-light';
-export * from './toast';
-export * from './notification';
-export * from './rounded-wrapper';
-export * from './maintenance-page';
diff --git a/libs/ui-toolkit/src/components/input-error/input-error.tsx b/libs/ui-toolkit/src/components/input-error/input-error.tsx
index 586c885f3..b79429645 100644
--- a/libs/ui-toolkit/src/components/input-error/input-error.tsx
+++ b/libs/ui-toolkit/src/components/input-error/input-error.tsx
@@ -13,6 +13,7 @@ export const InputError = ({
children,
forInput,
testId,
+ className,
...props
}: InputErrorProps) => {
const effectiveClassName = classNames(
@@ -31,7 +32,7 @@ export const InputError = ({
diff --git a/libs/ui-toolkit/src/components/navigation/index.ts b/libs/ui-toolkit/src/components/navigation/index.ts
new file mode 100644
index 000000000..701e50504
--- /dev/null
+++ b/libs/ui-toolkit/src/components/navigation/index.ts
@@ -0,0 +1 @@
+export * from './navigation';
diff --git a/libs/ui-toolkit/src/components/navigation/navigation.stories.tsx b/libs/ui-toolkit/src/components/navigation/navigation.stories.tsx
new file mode 100644
index 000000000..dd874d699
--- /dev/null
+++ b/libs/ui-toolkit/src/components/navigation/navigation.stories.tsx
@@ -0,0 +1,246 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { MemoryRouter, Route, Routes, useMatch } from 'react-router-dom';
+import { Button, ExternalLink, Icon, ThemeSwitcher } from '..';
+import {
+ Navigation,
+ NavigationBreakpoint,
+ NavigationContent,
+ NavigationItem,
+ NavigationLink,
+ NavigationList,
+ NavigationTrigger,
+} from './navigation';
+
+export default {
+ title: 'Navigation',
+ component: Navigation,
+} as ComponentMeta
;
+
+const Template: ComponentStory = ({
+ children,
+ ...props
+}) => {
+ const nav = {children};
+ return (
+
+
+ {nav}
+
+
+ Transactions} />
+ Blocks} />
+ All markets} />
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis
+ pariatur a nemo quos sed! Voluptas itaque voluptate dolores minima.
+ Iste laudantium perspiciatis accusamus facere eius repudiandae sit
+ odio saepe nisi.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea facere
+ ab at incidunt numquam nemo natus eos iure, iste tenetur illo
+ dolores, commodi magni quam dolor totam quae velit eaque.
+
+
+ Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quaerat
+ minus perspiciatis quas temporibus odit? Enim ipsam nisi amet
+ molestias magnam esse blanditiis aperiam sapiente quaerat. Veniam
+ unde magnam exercitationem distinctio.
+
+
+
+
+ );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ appName: '',
+};
+
+const ExplorerNav = () => {
+ const isOnMarkets = useMatch('markets/*');
+
+ return (
+ <>
+
+
+ {/* */}
+
+
+
+
+
+ Transactions
+
+
+ Blocks
+
+
+
+ Markets
+
+
+
+
+ All markets
+
+
+
+ Proposed markets
+
+
+
+
+ Failed markets
+
+
+
+
+
+
+ Validators
+
+
+ >
+ );
+};
+
+export const Explorer = Template.bind({});
+Explorer.args = {
+ appName: 'Explorer',
+ theme: 'system',
+ children: ,
+ actions: (
+ <>
+
+ {/* JUST A PLACEHOLDER */}
+
+
+
+
+ >
+ ),
+};
+
+const ConsoleNav = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+ Markets
+
+
+ Trading
+
+
+ Portfolio
+
+
+
+
+ Governance
+
+
+
+
+ >
+ );
+};
+
+export const Console = Template.bind({});
+Console.args = {
+ appName: 'Console',
+ theme: 'system',
+ children: ,
+ breakpoints: [478, 770],
+ actions: ,
+};
+
+const GovernanceNav = () => {
+ const isOnToken = useMatch('token/*');
+ return (
+ <>
+
+
+
+
+
+
+
+ Proposals
+
+
+ Validators
+
+
+ Rewards
+
+
+
+ Token
+
+
+
+
+ Token
+
+
+
+ Supply & Vesting
+
+
+
+ Withdraw
+
+
+ Redeem
+
+
+ Associate
+
+
+
+ Disassociate
+
+
+
+
+
+
+ >
+ );
+};
+
+export const Governance = Template.bind({});
+Governance.args = {
+ appName: 'Governance',
+ theme: 'dark',
+ children: ,
+ actions: (
+
+ ),
+};
diff --git a/libs/ui-toolkit/src/components/navigation/navigation.tsx b/libs/ui-toolkit/src/components/navigation/navigation.tsx
new file mode 100644
index 000000000..93c3cfc37
--- /dev/null
+++ b/libs/ui-toolkit/src/components/navigation/navigation.tsx
@@ -0,0 +1,491 @@
+import classNames from 'classnames';
+import type { ComponentProps, ReactNode } from 'react';
+import { createContext } from 'react';
+import { useContext } from 'react';
+import { useRef } from 'react';
+import { VegaLogo } from '../vega-logo';
+import * as NavigationMenu from '@radix-ui/react-navigation-menu';
+import { useResizeObserver } from '@vegaprotocol/react-helpers';
+import { Icon } from '../icon';
+import { Drawer, DrawerContext, useDrawer } from '../drawer';
+import type { Link } from 'react-router-dom';
+import { NavLink } from 'react-router-dom';
+
+type NavigationProps = {
+ /**
+ * The display name of the dApp, e.g. "Console", "Explorer"
+ */
+ appName: string;
+ /**
+ * URL pointing to the home page.
+ */
+ homeLink?: string;
+ /**
+ * The theme of the navigation.
+ * @default "system"
+ */
+ theme: 'system' | 'light' | 'dark' | 'yellow';
+ /**
+ * The navigation items (links, buttons, dropdowns, etc.)
+ */
+ children?: ReactNode;
+ /**
+ * The navigation actions (e.g. theme switcher, wallet connector)
+ */
+ actions?: ReactNode;
+ /**
+ * Size variants breakpoints
+ */
+ breakpoints?: [number, number];
+};
+
+export enum NavigationBreakpoint {
+ /**
+ * Full width navigation
+ * `width > breakpoints[1]`
+ */
+ Full = 'nav-size-full',
+ /**
+ * Narrow variant
+ * `width > breakpoints[0] && width <= breakpoints[1]`
+ */
+ Narrow = 'nav-size-narrow',
+ /**
+ * Small variant
+ * `width <= breakpoints[0]`
+ */
+ Small = 'nav-size-small',
+}
+type NavigationElementProps = {
+ hide?: NavigationBreakpoint[];
+ hideInDrawer?: boolean;
+};
+
+const determineIfHidden = ({ hide, hideInDrawer }: NavigationElementProps) => [
+ {
+ '[.nav-size-full_.navbar_&]:hidden': hide?.includes(
+ NavigationBreakpoint.Full
+ ),
+ '[.nav-size-narrow_.navbar_&]:hidden': hide?.includes(
+ NavigationBreakpoint.Narrow
+ ),
+ '[.nav-size-small_.navbar_&]:hidden': hide?.includes(
+ NavigationBreakpoint.Small
+ ),
+ '[.drawer-content_&]:hidden': hideInDrawer,
+ },
+];
+
+const Logo = ({
+ appName,
+ homeLink,
+}: Pick) => {
+ const theme = useContext(NavigationThemeContext);
+ return (
+
+ {homeLink ? (
+
+
+
+ ) : (
+
+ )}
+
+ {appName && (
+
+ {appName}
+
+ )}
+
+ );
+};
+
+const Spacer = () => ;
+
+export const NavigationItem = ({
+ children,
+ className,
+ hide,
+ hideInDrawer,
+ ...props
+}: ComponentProps & NavigationElementProps) => {
+ const insideDrawer = useContext(DrawerContext);
+ return (
+
+ {children}
+
+ );
+};
+export const NavigationList = ({
+ children,
+ className,
+ hide,
+ hideInDrawer,
+ ...props
+}: ComponentProps & NavigationElementProps) => (
+
+ {children}
+
+);
+export const NavigationTrigger = ({
+ children,
+ className,
+ isActive = false,
+ hide,
+ hideInDrawer,
+ ...props
+}: ComponentProps & {
+ isActive?: boolean;
+} & NavigationElementProps) => {
+ const theme = useContext(NavigationThemeContext);
+ return (
+ e.preventDefault()} // disables hover
+ onPointerLeave={(e) => e.preventDefault()} // disables hover
+ {...props}
+ >
+ {children}
+
+
+
+
+
+ );
+};
+export const NavigationContent = ({
+ children,
+ className,
+ ...props
+}: ComponentProps) => {
+ const theme = useContext(NavigationThemeContext);
+ const insideDrawer = useContext(DrawerContext);
+
+ const content = (
+ e.preventDefault()} // disables hover
+ asChild
+ {...props}
+ >
+
+ {children}
+
+
+ );
+
+ const list = (
+
+ {children}
+
+ );
+
+ return insideDrawer ? list : content;
+};
+
+export const NavigationLink = ({
+ children,
+ to,
+ ...props
+}: ComponentProps) => {
+ const theme = useContext(NavigationThemeContext);
+ const setDrawerOpen = useDrawer((state) => state.setDrawerOpen);
+ return (
+ {
+ setDrawerOpen(false);
+ }}
+ >
+
+ {({ isActive }) => (
+ <>
+
+ {children}
+
+
+ >
+ )}
+
+
+ );
+};
+
+const NavigationThemeContext =
+ createContext('system');
+
+export const Navigation = ({
+ appName,
+ homeLink = '/',
+ children,
+ actions,
+ theme = 'system',
+ breakpoints = [478, 1000],
+}: NavigationProps) => {
+ const navigationRef = useRef(null);
+ const actionsRef = useRef(null);
+ useResizeObserver(navigationRef.current, (entries) => {
+ if (entries.length === 0 && !navigationRef.current) return;
+
+ const width = entries[0].borderBoxSize[0].inlineSize;
+ navigationRef.current?.classList.remove(
+ NavigationBreakpoint.Full,
+ NavigationBreakpoint.Narrow,
+ NavigationBreakpoint.Small
+ );
+
+ if (width <= breakpoints[0]) {
+ navigationRef.current?.classList.add(NavigationBreakpoint.Small);
+ }
+ if (width > breakpoints[0] && width <= breakpoints[1]) {
+ navigationRef.current?.classList.add(NavigationBreakpoint.Narrow);
+ }
+ if (width > breakpoints[1]) {
+ navigationRef.current?.classList.add(NavigationBreakpoint.Full);
+ }
+ });
+
+ const [drawerOpen, setDrawerOpen] = useDrawer((state) => [
+ state.drawerOpen,
+ state.setDrawerOpen,
+ ]);
+
+ const drawerTrigger = (
+
+ );
+
+ const drawerContent = (
+
+
+
+ );
+
+ return (
+
+
+
+
+ {children}
+
+
+ {(actions || children) && (
+
+ {actions}
+
+ {drawerContent}
+
+
+ )}
+
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/theme-switcher/icons.tsx b/libs/ui-toolkit/src/components/theme-switcher/icons.tsx
index 88ad0d3ce..481b506aa 100644
--- a/libs/ui-toolkit/src/components/theme-switcher/icons.tsx
+++ b/libs/ui-toolkit/src/components/theme-switcher/icons.tsx
@@ -1,5 +1,9 @@
-export const SunIcon = () => (
-