Commit 7979e416 authored by tom's avatar tom

fix network menu

parent be5b304d
import { forwardRef } from 'react';
import { Button, type ButtonProps } from './button';
export interface IconButtonProps extends ButtonProps {}
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
function IconButton(props, ref) {
return (
<Button
px="0"
py="0"
height="auto"
minW="auto"
ref={ ref }
{ ...props }
/>
);
},
);
......@@ -94,6 +94,29 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
end: { value: { base: '{colors.blackAlpha.100}', _dark: '{colors.whiteAlpha.100}' } },
},
},
tabs: {
solid: {
fg: {
DEFAULT: { value: { base: '{colors.blue.700}', _dark: '{colors.gray.400}' } },
selected: { value: { base: '{colors.blue.700}', _dark: '{colors.gray.50}' } },
},
bg: {
selected: { value: { base: '{colors.blue.50}', _dark: '{colors.gray.800}' } },
},
},
secondary: {
fg: {
DEFAULT: { value: { base: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
selected: { value: { base: '{colors.blue.600}', _dark: '{colors.gray.50}' } },
},
bg: {
selected: { value: { base: '{colors.blue.50}', _dark: '{colors.gray.600}' } },
},
border: {
DEFAULT: { value: { base: '{colors.gray.200}', _dark: '{colors.gray.600}' } },
},
},
},
text: {
primary: { value: { base: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
secondary: { value: { base: '{colors.gray.500}', _dark: '{colors.gray.400}' } },
......
......@@ -95,6 +95,14 @@ export const recipe = defineRecipe({
},
},
},
plain: {
bg: 'transparent',
color: 'inherit',
border: 'none',
_hover: {
bg: 'transparent',
},
},
},
size: {
xs: { px: 2, h: 6, fontSize: '12px' },
......
......@@ -3,6 +3,7 @@ import { recipe as link } from './link.recipe';
import { recipe as popover } from './popover.recipe';
import { recipe as progressCircle } from './progress-circle.recipe';
import { recipe as skeleton } from './skeleton.recipe';
import { recipe as tabs } from './tabs.recipe';
import { recipe as tooltip } from './tooltip.recipe';
export const recipes = {
......@@ -12,7 +13,8 @@ export const recipes = {
};
export const slotRecipes = {
tooltip,
popover,
progressCircle,
tabs,
tooltip,
};
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
slots: [ 'root', 'list', 'trigger', 'content', 'indicator' ],
base: {
root: {
'--tabs-trigger-radius': 'radii.l2',
position: 'relative',
_horizontal: {
display: 'block',
},
_vertical: {
display: 'flex',
},
},
list: {
display: 'inline-flex',
position: 'relative',
isolation: 'isolate',
'--tabs-indicator-shadow': 'shadows.xs',
'--tabs-indicator-bg': 'colors.bg',
minH: 'var(--tabs-height)',
_horizontal: {
flexDirection: 'row',
},
_vertical: {
flexDirection: 'column',
},
},
trigger: {
outline: '0',
minW: 'var(--tabs-height)',
height: 'var(--tabs-height)',
display: 'flex',
alignItems: 'center',
fontWeight: 'medium',
position: 'relative',
cursor: 'button',
gap: '2',
_focusVisible: {
zIndex: 1,
outline: '2px solid',
outlineColor: 'colorPalette.focusRing',
},
_disabled: {
cursor: 'not-allowed',
opacity: 0.5,
},
},
content: {
focusVisibleRing: 'inside',
_horizontal: {
width: '100%',
pt: 'var(--tabs-content-padding)',
},
_vertical: {
height: '100%',
ps: 'var(--tabs-content-padding)',
},
},
indicator: {
width: 'var(--width)',
height: 'var(--height)',
borderRadius: 'var(--tabs-indicator-radius)',
bg: 'var(--tabs-indicator-bg)',
shadow: 'var(--tabs-indicator-shadow)',
zIndex: -1,
},
},
variants: {
fitted: {
'true': {
list: {
display: 'flex',
},
trigger: {
flex: 1,
textAlign: 'center',
justifyContent: 'center',
},
},
},
justify: {
start: {
list: {
justifyContent: 'flex-start',
},
},
center: {
list: {
justifyContent: 'center',
},
},
end: {
list: {
justifyContent: 'flex-end',
},
},
},
size: {
sm: {
root: {
'--tabs-height': 'sizes.8',
'--tabs-content-padding': 'spacing.6',
},
trigger: {
py: '1',
px: '3',
textStyle: 'sm',
},
},
md: {
root: {
'--tabs-height': 'sizes.10',
'--tabs-content-padding': 'spacing.6',
},
trigger: {
py: '2',
px: '4',
textStyle: 'md',
},
},
},
variant: {
solid: {
trigger: {
fontWeight: '600',
borderRadius: 'base',
color: 'tabs.solid.fg',
bg: 'transparent',
_selected: {
bg: 'tabs.solid.bg.selected',
color: 'tabs.solid.fg.selected',
_hover: {
color: 'tabs.solid.fg.selected',
},
},
_hover: {
color: 'link.primary.hover',
},
},
},
secondary: {
list: {
border: 'none',
columnGap: '3',
_horizontal: {
_before: {
display: 'none',
},
},
},
trigger: {
fontWeight: '500',
color: 'tabs.secondary.fg',
bg: 'transparent',
borderWidth: '2px',
borderStyle: 'solid',
borderColor: 'tabs.secondary.border',
borderRadius: 'base',
_selected: {
bg: 'tabs.secondary.bg.selected',
color: 'tabs.secondary.fg.selected',
borderColor: 'tabs.secondary.bg.selected',
_hover: {
color: 'tabs.secondary.fg.selected',
borderColor: 'tabs.secondary.bg.selected',
},
},
_hover: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
},
},
},
},
},
defaultVariants: {
size: 'md',
variant: 'solid',
},
});
import { Heading, HStack, Link, VStack } from '@chakra-ui/react';
import { Heading, HStack, Link, Tabs, VStack } from '@chakra-ui/react';
import React from 'react';
import { Button } from 'toolkit/chakra/button';
......@@ -67,7 +67,6 @@ const ChakraShowcases = () => {
</HStack>
</section>
{ /* TODO @tom2drum check skeleton styles */ }
<section>
<Heading textStyle="heading.md" mb={ 2 }>Skeleton & Loaders</Heading>
<HStack gap={ 4 }>
......@@ -77,6 +76,28 @@ const ChakraShowcases = () => {
<ContentLoader/>
</HStack>
</section>
<section>
<Heading textStyle="heading.md" mb={ 2 }>Tabs</Heading>
<HStack gap={ 4 }>
<Tabs.Root defaultValue="tab1" variant="solid">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs.Root>
<Tabs.Root defaultValue="tab1" variant="secondary" size="sm">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs.Root>
</HStack>
</section>
</VStack>
</>
);
......
import { Box, Image, Skeleton, chakra } from '@chakra-ui/react';
import { Image, Skeleton, chakra } from '@chakra-ui/react';
import React from 'react';
import { route } from 'nextjs-routes';
......@@ -42,6 +42,7 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall
);
};
// TODO @tom2drum implement image with fallback
const NetworkLogo = ({ isCollapsed, onClick, className }: Props) => {
const logoSrc = useColorModeValue(config.UI.navigation.logo.default, config.UI.navigation.logo.dark || config.UI.navigation.logo.default);
......@@ -51,9 +52,8 @@ const NetworkLogo = ({ isCollapsed, onClick, className }: Props) => {
const iconStyle = useColorModeValue({}, !config.UI.navigation.icon.dark ? darkModeFilter : {});
return (
<Box
<chakra.a
className={ className }
as="a"
href={ route({ pathname: '/' }) }
width={{ base: '120px', lg: isCollapsed === false ? '120px' : '30px', xl: isCollapsed ? '30px' : '120px' }}
height={{ base: '24px', lg: isCollapsed === false ? '24px' : '30px', xl: isCollapsed ? '30px' : '24px' }}
......@@ -83,7 +83,7 @@ const NetworkLogo = ({ isCollapsed, onClick, className }: Props) => {
display={{ base: 'none', lg: isCollapsed === false ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}
style={ iconStyle }
/>
</Box>
</chakra.a>
);
};
......
import { PopoverContent, PopoverBody, Tabs, TabList, TabPanels, TabPanel, Tab, VStack, Skeleton, Flex, useColorModeValue } from '@chakra-ui/react';
import { Tabs, VStack, Skeleton, Flex, Box } from '@chakra-ui/react';
import React from 'react';
import type { FeaturedNetwork, NetworkGroup } from 'types/networks';
import { PopoverBody, PopoverContent } from 'toolkit/chakra/popover';
import NetworkMenuLink from './NetworkMenuLink';
interface Props {
......@@ -12,20 +14,18 @@ interface Props {
const NetworkMenuPopup = ({ items, tabs }: Props) => {
const selectedNetwork = items?.find(({ isActive }) => isActive);
const defaultTab = tabs.findIndex((tab) => selectedNetwork?.group === tab);
const [ tabIndex, setTabIndex ] = React.useState(defaultTab > -1 ? defaultTab : 0);
const defaultTab = tabs.find((tab) => selectedNetwork?.group === tab);
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const [ value, setValue ] = React.useState<NetworkGroup>(defaultTab ?? 'Mainnets');
const handleTabChange = React.useCallback((index: number) => {
setTabIndex(index);
const handleTabChange = React.useCallback(({ value }: { value: string }) => {
setValue(value as NetworkGroup);
}, []);
const content = !items || items.length === 0 ? (
<>
<Flex alignItems="center">
<Flex h="32px" w="105px" bgColor={ bgColor } borderRadius="base" px={ 4 } py={ 2 }>
<Flex h="32px" w="105px" bgColor={{ base: 'blackAlpha.50', _dark: 'whiteAlpha.50' }} borderRadius="base" px={ 4 } py={ 2 }>
<Skeleton h="16px" w="100%"/>
</Flex>
<Skeleton h="16px" w="68px" mx={ 4 }/>
......@@ -47,27 +47,30 @@ const NetworkMenuPopup = ({ items, tabs }: Props) => {
</Flex>
</>
) : (
<Tabs
variant="outline"
colorScheme="gray"
<Tabs.Root
variant="secondary"
size="sm"
isLazy
index={ tabIndex }
onChange={ handleTabChange }
lazyMount
value={ value }
onValueChange={ handleTabChange }
>
{ tabs.length > 1 && (
<TabList columnGap={ 2 }>
{ tabs.map((tab, index) => (
<Tab key={ tab } textTransform="capitalize" { ...(tabIndex === index ? { 'data-selected': 'true' } : {}) }>
<Tabs.List columnGap={ 2 } mb={ 4 }>
{ tabs.map((tab) => (
<Tabs.Trigger
key={ tab }
textTransform="capitalize"
value={ tab }
>
{ tab }
</Tab>
</Tabs.Trigger>
)) }
</TabList>
</Tabs.List>
) }
<TabPanels mt={ 3 }>
<Box>
{ tabs.map((tab) => (
<TabPanel key={ tab } p={ 0 }>
<VStack as="ul" spacing={ 1 } alignItems="stretch" mt={ 4 } maxH="516px" overflowY="scroll">
<Tabs.Content key={ tab } value={ tab } p={ 0 }>
<VStack as="ul" gap={ 1 } alignItems="stretch" maxH="516px" overflowY="scroll">
{ items
.filter((network) => network.group === tab)
.map((network) => (
......@@ -77,10 +80,10 @@ const NetworkMenuPopup = ({ items, tabs }: Props) => {
/>
)) }
</VStack>
</TabPanel>
</Tabs.Content>
)) }
</TabPanels>
</Tabs>
</Box>
</Tabs.Root>
);
return (
......
import { Box, Flex, Text, Image, useColorModeValue } from '@chakra-ui/react';
import { Box, Text, Image, chakra } from '@chakra-ui/react';
import React from 'react';
import type { FeaturedNetwork } from 'types/networks';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import IconSvg from 'ui/shared/IconSvg';
import useColors from './useColors';
interface Props extends FeaturedNetwork {
isActive?: boolean;
isMobile?: boolean;
}
const NetworkMenuLink = ({ title, icon, isActive, isMobile, url, invertIconInDarkMode }: Props) => {
const colors = useColors();
const darkModeFilter = { filter: 'brightness(0) invert(1)' };
const style = useColorModeValue({}, invertIconInDarkMode ? darkModeFilter : {});
......@@ -23,14 +21,14 @@ const NetworkMenuLink = ({ title, icon, isActive, isMobile, url, invertIconInDar
<IconSvg
name="networks/icon-placeholder"
boxSize="30px"
color={ colors.iconPlaceholder.default }
color={{ base: 'blackAlpha.100', _dark: 'whiteAlpha.300' }}
/>
);
return (
<Box as="li" listStyleType="none">
<Flex
as="a"
<chakra.a
display="flex"
href={ url }
px={ 3 }
py="9px"
......@@ -38,9 +36,9 @@ const NetworkMenuLink = ({ title, icon, isActive, isMobile, url, invertIconInDar
cursor="pointer"
pointerEvents={ isActive ? 'none' : 'initial' }
borderRadius="base"
color={ isActive ? colors.text.active : colors.text.default }
bgColor={ isActive ? colors.bg.active : colors.bg.default }
_hover={{ color: isActive ? colors.text.active : colors.text.hover }}
color={ isActive ? { base: 'blackAlpha.900', _dark: 'whiteAlpha.900' } : { base: 'gray.600', _dark: 'gray.400' } }
bgColor={ isActive ? { base: 'blue.50', _dark: 'whiteAlpha.100' } : 'transparent' }
_hover={{ color: isActive ? { base: 'blackAlpha.900', _dark: 'whiteAlpha.900' } : 'link.primary.hover' }}
>
{ iconEl }
<Text
......@@ -59,7 +57,7 @@ const NetworkMenuLink = ({ title, icon, isActive, isMobile, url, invertIconInDar
marginLeft="auto"
/>
) }
</Flex>
</chakra.a>
</Box>
);
};
......
import { useColorModeValue } from '@chakra-ui/react';
export default function useColors() {
return {
text: {
'default': useColorModeValue('gray.600', 'gray.400'),
active: useColorModeValue('blackAlpha.900', 'whiteAlpha.900'),
hover: 'link_hovered',
},
iconPlaceholder: {
'default': useColorModeValue('blackAlpha.100', 'whiteAlpha.300'),
},
bg: {
'default': 'transparent',
active: useColorModeValue('blue.50', 'whiteAlpha.100'),
},
border: {
'default': 'border.divider',
active: useColorModeValue('blue.50', 'whiteAlpha.100'),
},
};
}
......@@ -10,23 +10,32 @@ import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';
export default function useNetworkMenu() {
const { isOpen, onClose, onOpen, onToggle } = useDisclosure();
const { open, onClose, onOpen, onToggle } = useDisclosure();
const fetch = useFetch();
const { isPending, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>({
queryKey: [ 'featured-network' ],
queryFn: async() => fetch(config.UI.navigation.featuredNetworks || '', undefined, { resource: 'featured-network' }),
enabled: Boolean(config.UI.navigation.featuredNetworks) && isOpen,
enabled: Boolean(config.UI.navigation.featuredNetworks) && open,
staleTime: Infinity,
});
const onOpenChange = React.useCallback(({ open }: { open: boolean }) => {
if (open) {
onOpen();
} else {
onClose();
}
}, [ onOpen, onClose ]);
return React.useMemo(() => ({
isOpen,
open,
onClose,
onOpen,
onToggle,
onOpenChange,
isPending,
data,
availableTabs: NETWORK_GROUPS.filter((tab) => data?.some(({ group }) => group === tab)),
}), [ isOpen, onClose, onOpen, onToggle, data, isPending ]);
}), [ open, onClose, onOpen, onToggle, onOpenChange, data, isPending ]);
}
import { IconButton, PopoverTrigger } from '@chakra-ui/react';
import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import { IconButton } from 'toolkit/chakra/icon-button';
import { PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
import IconSvg from 'ui/shared/IconSvg';
import NetworkMenuContentDesktop from 'ui/snippets/networkMenu/NetworkMenuContentDesktop';
import useNetworkMenu from 'ui/snippets/networkMenu/useNetworkMenu';
......@@ -10,21 +10,26 @@ const NetworkMenu = () => {
const menu = useNetworkMenu();
return (
<Popover placement="bottom-start" trigger="click" isLazy isOpen={ menu.isOpen } onClose={ menu.onClose }>
<PopoverRoot
positioning={{ placement: 'bottom-start' }}
lazyMount
open={ menu.open }
onOpenChange={ menu.onOpenChange }
>
<PopoverTrigger>
<IconButton
variant="simple"
colorScheme="blue"
visual="plain"
color="link.primary"
_hover={{ color: 'link.primary.hover' }}
aria-label="Network menu"
icon={ <IconSvg name="networks" boxSize={ 4 }/> }
p="1px"
boxSize={ 4 }
borderRadius="none"
onClick={ menu.onToggle }
/>
>
<IconSvg name="networks" boxSize={ 4 } p="1px"/>
</IconButton>
</PopoverTrigger>
<NetworkMenuContentDesktop items={ menu.data } tabs={ menu.availableTabs }/>
</Popover>
</PopoverRoot>
);
};
......
......@@ -32,12 +32,12 @@ const TopBar = () => {
</>
) } */ }
{ /* <Settings/> */ }
{ /* { config.UI.navigation.layout === 'horizontal' && Boolean(config.UI.navigation.featuredNetworks) && (
{ config.UI.navigation.layout === 'horizontal' && Boolean(config.UI.navigation.featuredNetworks) && (
<Box display={{ base: 'none', lg: 'flex' }}>
<Separator mx={ 3 } height={ 4 } orientation="vertical"/>
<NetworkMenu/>
</Box>
) } */ }
) }
</Flex>
</Flex>
</Box>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment