mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
feat: unified header bar with Lux Network branding
- Redesign TopBar as single unified header: logo + nav + controls - Add NetworkSelector dropdown (Mainnet/Testnet/Devnet) - Monochrome ChainSwitcher (kill blue panel) - Connect wallet button via UserProfileDesktop in header - Remove NavBar and SideBar (nav now in TopBar) - Simplify HeaderDesktop to search bar only
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
import config from 'configs/app';
|
||||
import NavigationDesktop from 'ui/snippets/navigation/horizontal/NavigationDesktop';
|
||||
const NavBar = () => null;
|
||||
|
||||
const EmptyComponent = () => null;
|
||||
|
||||
export default config.UI.navigation.layout === 'horizontal' ? NavigationDesktop : EmptyComponent;
|
||||
export default NavBar;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import config from 'configs/app';
|
||||
import NavigationDesktop from 'ui/snippets/navigation/vertical/NavigationDesktop';
|
||||
const SideBar = () => null;
|
||||
|
||||
const EmptyComponent = () => null;
|
||||
|
||||
export default config.UI.navigation.layout === 'horizontal' ? EmptyComponent : NavigationDesktop;
|
||||
export default SideBar;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { HStack, Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import RewardsButton from 'ui/rewards/RewardsButton';
|
||||
import SearchBar from 'ui/snippets/searchBar/SearchBarDesktop';
|
||||
import UserProfileDesktop from 'ui/snippets/user/UserProfileDesktop';
|
||||
|
||||
type Props = {
|
||||
renderSearchBar?: () => React.ReactNode;
|
||||
@@ -26,12 +23,6 @@ const HeaderDesktop = ({ renderSearchBar }: Props) => {
|
||||
<Box width="100%">
|
||||
{ searchBar }
|
||||
</Box>
|
||||
{ config.UI.navigation.layout === 'vertical' && (
|
||||
<Box display="flex" gap={ 2 } flexShrink={ 0 }>
|
||||
{ config.features.rewards.isEnabled && <RewardsButton/> }
|
||||
<UserProfileDesktop buttonVariant="header"/>
|
||||
</Box>
|
||||
) }
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ const CHAINS: ReadonlyArray<ChainItem> = [
|
||||
{ name: 'Zoo', label: 'Zoo Chain', vm: 'Subnet EVM', url: 'https://explore-zoo.lux.network', isCurrent: false },
|
||||
{ name: 'Hanzo', label: 'Hanzo AI', vm: 'Subnet EVM', url: 'https://explore-hanzo.lux.network', isCurrent: false },
|
||||
{ name: 'SPC', label: 'SPC Chain', vm: 'Subnet EVM', url: 'https://explore-spc.lux.network', isCurrent: false },
|
||||
{ name: 'Pars', label: 'Pars Network', vm: 'Subnet EVM', url: 'https://explore.pars.network', isCurrent: false },
|
||||
{ name: 'Pars', label: 'Pars Network', vm: 'Subnet EVM', url: 'https://explore-pars.lux.network', isCurrent: false },
|
||||
];
|
||||
|
||||
const ChainSwitcher = () => {
|
||||
@@ -33,6 +33,8 @@ const ChainSwitcher = () => {
|
||||
setOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const current = CHAINS.find((c) => c.isCurrent);
|
||||
|
||||
return (
|
||||
<PopoverRoot
|
||||
positioning={{ placement: 'bottom-end', offset: { mainAxis: 6 } }}
|
||||
@@ -47,10 +49,10 @@ const ChainSwitcher = () => {
|
||||
fontWeight={ 500 }
|
||||
onClick={ handleToggle }
|
||||
borderColor="border.divider"
|
||||
px={ 3 }
|
||||
px={ 2.5 }
|
||||
>
|
||||
<IconSvg name="networks" boxSize="14px"/>
|
||||
<span>C-Chain</span>
|
||||
<span>{ current?.name ?? 'C-Chain' }</span>
|
||||
<IconSvg name="arrows/east-mini" boxSize={ 4 } transform="rotate(-90deg)"/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -72,7 +74,7 @@ const ChainSwitcher = () => {
|
||||
px={ 3 }
|
||||
py={ 2.5 }
|
||||
cursor={ chain.isCurrent ? 'default' : 'pointer' }
|
||||
_hover={ chain.isCurrent ? {} : { bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' } } }
|
||||
_hover={ chain.isCurrent ? {} : { bg: { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' } } }
|
||||
transition="background 0.15s"
|
||||
borderBottom="1px solid"
|
||||
borderColor="border.divider"
|
||||
@@ -85,11 +87,7 @@ const ChainSwitcher = () => {
|
||||
{ chain.name }
|
||||
</Text>
|
||||
{ chain.isCurrent && (
|
||||
<Box
|
||||
bgColor="green.400"
|
||||
borderRadius="full"
|
||||
boxSize="6px"
|
||||
/>
|
||||
<Box boxSize="6px" borderRadius="full" bgColor="green.400"/>
|
||||
) }
|
||||
</Flex>
|
||||
<Text fontSize="xs" color="text.secondary">
|
||||
@@ -97,7 +95,7 @@ const ChainSwitcher = () => {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Box
|
||||
bgColor={{ _light: 'gray.100', _dark: 'whiteAlpha.100' }}
|
||||
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.100' }}
|
||||
color="text.secondary"
|
||||
borderRadius="sm"
|
||||
px={ 1.5 }
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Box, Flex, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from 'toolkit/chakra/button';
|
||||
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
interface NetworkItem {
|
||||
readonly name: string;
|
||||
readonly label: string;
|
||||
readonly url: string;
|
||||
readonly isCurrent: boolean;
|
||||
}
|
||||
|
||||
const NETWORKS: ReadonlyArray<NetworkItem> = [
|
||||
{ name: 'Mainnet', label: 'Production network', url: 'https://explore.lux.network', isCurrent: true },
|
||||
{ name: 'Testnet', label: 'Test network', url: 'https://explore.lux-test.network', isCurrent: false },
|
||||
{ name: 'Devnet', label: 'Development network', url: 'https://explore.lux-dev.network', isCurrent: false },
|
||||
];
|
||||
|
||||
const NetworkSelector = () => {
|
||||
const [ open, setOpen ] = React.useState(false);
|
||||
|
||||
const handleOpenChange = React.useCallback(({ open: isOpen }: { open: boolean }) => {
|
||||
setOpen(isOpen);
|
||||
}, []);
|
||||
|
||||
const handleToggle = React.useCallback(() => {
|
||||
setOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const current = NETWORKS.find((n) => n.isCurrent);
|
||||
|
||||
return (
|
||||
<PopoverRoot
|
||||
positioning={{ placement: 'bottom-end', offset: { mainAxis: 6 } }}
|
||||
lazyMount
|
||||
open={ open }
|
||||
onOpenChange={ handleOpenChange }
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="2xs"
|
||||
fontWeight={ 500 }
|
||||
onClick={ handleToggle }
|
||||
borderColor="border.divider"
|
||||
px={ 2.5 }
|
||||
>
|
||||
<Box boxSize="6px" borderRadius="full" bgColor="green.400" flexShrink={ 0 }/>
|
||||
<span>{ current?.name ?? 'Mainnet' }</span>
|
||||
<IconSvg name="arrows/east-mini" boxSize={ 4 } transform="rotate(-90deg)"/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent w="240px">
|
||||
<PopoverBody p={ 0 }>
|
||||
<Box px={ 3 } py={ 2 } borderBottom="1px solid" borderColor="border.divider">
|
||||
<Text fontSize="xs" fontWeight={ 600 } color="text.secondary" textTransform="uppercase" letterSpacing="wider">
|
||||
Network
|
||||
</Text>
|
||||
</Box>
|
||||
{ NETWORKS.map((network) => (
|
||||
<Box
|
||||
key={ network.name }
|
||||
as={ network.isCurrent ? 'div' : 'a' }
|
||||
{ ...(!network.isCurrent ? { href: network.url } : {}) }
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={ 3 }
|
||||
py={ 2.5 }
|
||||
cursor={ network.isCurrent ? 'default' : 'pointer' }
|
||||
_hover={ network.isCurrent ? {} : { bg: { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' } } }
|
||||
transition="background 0.15s"
|
||||
borderBottom="1px solid"
|
||||
borderColor="border.divider"
|
||||
_last={{ borderBottom: 'none' }}
|
||||
textDecoration="none"
|
||||
>
|
||||
<Flex direction="column">
|
||||
<Flex align="center" gap={ 2 }>
|
||||
<Text fontSize="sm" fontWeight={ 500 } color="text.primary">
|
||||
{ network.name }
|
||||
</Text>
|
||||
{ network.isCurrent && (
|
||||
<Box boxSize="6px" borderRadius="full" bgColor="green.400"/>
|
||||
) }
|
||||
</Flex>
|
||||
<Text fontSize="xs" color="text.secondary">
|
||||
{ network.label }
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)) }
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NetworkSelector);
|
||||
@@ -1,33 +1,110 @@
|
||||
import { Flex, Box, HStack } from '@chakra-ui/react';
|
||||
import { Flex, Box, HStack, chakra } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils';
|
||||
import UserProfileDesktop from 'ui/snippets/user/UserProfileDesktop';
|
||||
|
||||
import ChainSwitcher from './ChainSwitcher';
|
||||
import DeFiDropdown from './DeFiDropdown';
|
||||
import NetworkMenu from './NetworkMenu';
|
||||
import NetworkSelector from './NetworkSelector';
|
||||
|
||||
interface NavLinkProps {
|
||||
readonly text: string;
|
||||
readonly href: string;
|
||||
readonly isActive: boolean;
|
||||
}
|
||||
|
||||
const NavLinkItem = ({ text, href, isActive }: NavLinkProps) => (
|
||||
<Link
|
||||
href={ href }
|
||||
px={ 2.5 }
|
||||
py={ 1.5 }
|
||||
textStyle="sm"
|
||||
fontWeight={ 500 }
|
||||
borderRadius="base"
|
||||
variant="navigation"
|
||||
{ ...(isActive ? { 'data-selected': true } : {}) }
|
||||
textDecoration="none"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
{ text }
|
||||
</Link>
|
||||
);
|
||||
|
||||
const TopBar = () => {
|
||||
const hasDeFiDropdown = Boolean(config.features.deFiDropdown.isEnabled);
|
||||
const router = useRouter();
|
||||
const pathname = router.pathname;
|
||||
|
||||
const navItems = React.useMemo(() => [
|
||||
{ text: 'Home', href: route({ pathname: '/' as const }), isActive: pathname === '/' },
|
||||
{ text: 'Chains', href: route({ pathname: '/chains' as const }), isActive: pathname === '/chains' || pathname.startsWith('/chain/') },
|
||||
{ text: 'Validators', href: route({ pathname: '/validators' as const }), isActive: pathname === '/validators' || pathname.startsWith('/validators/') },
|
||||
{ text: 'Stats', href: route({ pathname: '/stats' as const }), isActive: pathname.startsWith('/stats') },
|
||||
{ text: 'Bridge', href: route({ pathname: '/bridge' as const }), isActive: pathname === '/bridge' },
|
||||
{ text: 'Blocks', href: route({ pathname: '/blocks' as const }), isActive: pathname === '/blocks' || pathname.startsWith('/block/') },
|
||||
{ text: 'Txns', href: route({ pathname: '/txs' as const }), isActive: pathname === '/txs' || pathname.startsWith('/tx/') },
|
||||
{ text: 'Tokens', href: route({ pathname: '/tokens' as const }), isActive: pathname === '/tokens' || pathname.startsWith('/token/') },
|
||||
], [ pathname ]);
|
||||
|
||||
return (
|
||||
// not ideal if scrollbar is visible, but better than having a horizontal scroll
|
||||
<Box bgColor={{ _light: 'theme.topbar.bg._light', _dark: 'theme.topbar.bg._dark' }} position="sticky" left={ 0 } width="100%" maxWidth="100vw">
|
||||
<Box
|
||||
bgColor={{ _light: 'rgba(255, 255, 255, 0.95)', _dark: 'rgba(16, 17, 18, 0.95)' }}
|
||||
position="sticky"
|
||||
top={ 0 }
|
||||
left={ 0 }
|
||||
width="100%"
|
||||
maxWidth="100vw"
|
||||
zIndex="sticky"
|
||||
borderBottom="1px solid"
|
||||
borderColor="border.divider"
|
||||
backdropFilter="blur(12px)"
|
||||
>
|
||||
<Flex
|
||||
py={ 2 }
|
||||
px={{ base: 3, lg: 6 }}
|
||||
m="0 auto"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
maxW={ `${ CONTENT_MAX_WIDTH }px` }
|
||||
gap={ 2 }
|
||||
>
|
||||
<HStack gap={ 2 } fontSize="xs">
|
||||
{ Boolean(config.UI.featuredNetworks.items) && <NetworkMenu/> }
|
||||
<ChainSwitcher/>
|
||||
{ /* Logo */ }
|
||||
<chakra.a
|
||||
href={ route({ pathname: '/' as const }) }
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap="8px"
|
||||
flexShrink={ 0 }
|
||||
mr={ 4 }
|
||||
aria-label="Lux Network home"
|
||||
textDecoration="none"
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="20" height="20">
|
||||
<polygon points="25,46.65 50,3.35 0,3.35" fill="currentColor"/>
|
||||
</svg>
|
||||
<chakra.span fontWeight={ 700 } fontSize="md" letterSpacing="-0.02em" whiteSpace="nowrap">
|
||||
Lux Network
|
||||
</chakra.span>
|
||||
</chakra.a>
|
||||
|
||||
{ /* Navigation */ }
|
||||
<HStack as="nav" gap={ 0 } display={{ base: 'none', lg: 'flex' }}>
|
||||
{ navItems.map((item) => (
|
||||
<NavLinkItem key={ item.text } { ...item }/>
|
||||
)) }
|
||||
</HStack>
|
||||
<HStack alignItems="center" gap={ 2 }>
|
||||
{ hasDeFiDropdown && <DeFiDropdown/> }
|
||||
|
||||
{ /* Spacer */ }
|
||||
<Box flex={ 1 }/>
|
||||
|
||||
{ /* Right controls */ }
|
||||
<HStack gap={ 2 } flexShrink={ 0 }>
|
||||
<NetworkSelector/>
|
||||
<ChainSwitcher/>
|
||||
<UserProfileDesktop buttonSize="sm"/>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user