feat: UI work on navbar responsivity (#244)
* feat: add useMediaQuery * feat: add menu icon * feat: add navbar responsivity * feat: add current path indication for navigation * feat: prevent screen zoom * refactor: move logo to navbar folder * refactor: move sidebar to single file * refactor: set icon names to kebab case * feat: add alpha color function * feat: add backdrop filter on navbar
This commit is contained in:
parent
cbe8b93594
commit
ae8ba51c84
|
|
@ -11,7 +11,10 @@
|
||||||
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
|
||||||
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Minimal UI for Sites as NFTs. Fleek XYZ"
|
content="Minimal UI for Sites as NFTs. Fleek XYZ"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter';
|
||||||
import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch';
|
import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch';
|
||||||
import { BiSearch } from '@react-icons/all-files/bi/BiSearch';
|
import { BiSearch } from '@react-icons/all-files/bi/BiSearch';
|
||||||
import { BsFillSquareFill } from '@react-icons/all-files/bs/BsFillSquareFill';
|
import { BsFillSquareFill } from '@react-icons/all-files/bs/BsFillSquareFill';
|
||||||
|
import { FaBars } from '@react-icons/all-files/fa/FaBars';
|
||||||
import { FaChevronRight } from '@react-icons/all-files/fa/FaChevronRight';
|
import { FaChevronRight } from '@react-icons/all-files/fa/FaChevronRight';
|
||||||
import { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt';
|
import { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt';
|
||||||
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
|
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
|
||||||
|
|
@ -26,7 +27,7 @@ import {
|
||||||
|
|
||||||
export const IconLibrary = Object.freeze({
|
export const IconLibrary = Object.freeze({
|
||||||
back: IoArrowBackCircleSharp,
|
back: IoArrowBackCircleSharp,
|
||||||
betaTag: BetaTag,
|
'beta-tag': BetaTag,
|
||||||
branch: BiGitBranch,
|
branch: BiGitBranch,
|
||||||
check: AiOutlineCheck,
|
check: AiOutlineCheck,
|
||||||
'check-circle': IoCheckmarkCircleSharp,
|
'check-circle': IoCheckmarkCircleSharp,
|
||||||
|
|
@ -36,10 +37,11 @@ export const IconLibrary = Object.freeze({
|
||||||
error: ErrorIcon,
|
error: ErrorIcon,
|
||||||
ethereum: EthereumIcon,
|
ethereum: EthereumIcon,
|
||||||
'external-link': FaExternalLinkAlt,
|
'external-link': FaExternalLinkAlt,
|
||||||
fleekLogo: FleekLogo,
|
'fleek-logo': FleekLogo,
|
||||||
fleekName: FleekName,
|
'fleek-name': FleekName,
|
||||||
github: IoLogoGithub,
|
github: IoLogoGithub,
|
||||||
info: IoInformationCircleSharp,
|
info: IoInformationCircleSharp,
|
||||||
|
menu: FaBars,
|
||||||
metamask: MetamaskIcon, //remove if not used
|
metamask: MetamaskIcon, //remove if not used
|
||||||
search: BiSearch,
|
search: BiSearch,
|
||||||
square: BsFillSquareFill,
|
square: BsFillSquareFill,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export const ConnectWalletButton: React.FC = () => {
|
||||||
if (ensName && address) setEnsNameStore(ensName, address);
|
if (ensName && address) setEnsNameStore(ensName, address);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={show}>
|
<Button onClick={show} css={{ gridArea: 'wallet' }}>
|
||||||
{isConnected && !!address && !!truncatedAddress ? (
|
{isConnected && !!address && !!truncatedAddress ? (
|
||||||
<Flex css={{ gap: '$2' }}>
|
<Flex css={{ gap: '$2' }}>
|
||||||
<Avatar address={address} size={20} />
|
<Avatar address={address} size={20} />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Icon } from '../../core/icon';
|
||||||
|
import { NavBarStyles as S } from './nav-bar.styles';
|
||||||
|
|
||||||
|
export const Logo: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<S.Logo.Wrapper onClick={() => navigate('/home')}>
|
||||||
|
<Icon
|
||||||
|
name="fleek-logo"
|
||||||
|
css={{ fontSize: '$2xl' }}
|
||||||
|
iconElementCss={{ height: '$6' }}
|
||||||
|
/>
|
||||||
|
<Icon name="fleek-name" css={{ fontSize: '$6xl', mr: '$3' }} />
|
||||||
|
<Icon name="beta-tag" css={{ fontSize: '$5xl' }} />
|
||||||
|
</S.Logo.Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,33 +1,129 @@
|
||||||
import { styled } from '@/theme';
|
import { StyledButton } from '@/components/core';
|
||||||
|
import { alphaColor, styled } from '@/theme';
|
||||||
|
|
||||||
import { Flex } from '../flex.styles';
|
import { Flex } from '../flex.styles';
|
||||||
|
|
||||||
export abstract class NavBarStyles {
|
export const NavBarStyles = {
|
||||||
static readonly Container = styled('header', {
|
Container: styled('header', {
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: '$black',
|
|
||||||
zIndex: '$sticky',
|
zIndex: '$sticky',
|
||||||
height: '$22',
|
height: '$22',
|
||||||
overflow: 'hidden', // TODO: this must be worked on for responsive layout
|
overflow: 'hidden', // TODO: this must be worked on for responsive layout
|
||||||
});
|
|
||||||
|
|
||||||
static readonly Content = styled('div', {
|
'&:after': {
|
||||||
display: 'flex',
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
backgroundColor: alphaColor('black', 0.8),
|
||||||
|
backdropFilter: 'blur(4px)',
|
||||||
|
zIndex: -1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
Content: styled('div', {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '$7xl',
|
maxWidth: '$7xl',
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '$6',
|
padding: '$6',
|
||||||
});
|
gap: '$3',
|
||||||
|
|
||||||
static readonly Navigation = styled(Flex, {
|
display: 'grid',
|
||||||
gap: '$10',
|
gridTemplateAreas: '"logo navigation wallet menu"',
|
||||||
flexGrow: 4,
|
gridTemplateColumns: 'auto 1fr auto',
|
||||||
justifyContent: 'center',
|
}),
|
||||||
});
|
|
||||||
}
|
Navigation: {
|
||||||
|
Container: styled(Flex, {
|
||||||
|
gridArea: 'navigation',
|
||||||
|
gap: '$10',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
fontSize: '$lg',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
stacked: {
|
||||||
|
true: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '$4',
|
||||||
|
|
||||||
|
[`${StyledButton}`]: {
|
||||||
|
fontSize: '$lg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
Button: styled(StyledButton, {
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
true: {
|
||||||
|
color: '$slate12 !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Sidebar: {
|
||||||
|
Content: styled(Flex, {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
padding: '$6',
|
||||||
|
minWidth: '40vw',
|
||||||
|
zIndex: '$sticky',
|
||||||
|
backgroundColor: '$black',
|
||||||
|
transition: 'transform 0.3s ease-in-out',
|
||||||
|
borderLeft: '1px solid $slate6',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
open: {
|
||||||
|
true: {
|
||||||
|
transform: 'translateX(0%)',
|
||||||
|
},
|
||||||
|
false: {
|
||||||
|
transform: 'translateX(100%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
Backdrop: styled('div', {
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
zIndex: '$sticky',
|
||||||
|
backgroundColor: alphaColor('black', 0.5),
|
||||||
|
display: 'none',
|
||||||
|
transition: 'opacity 0.3s ease-in-out',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
open: {
|
||||||
|
true: {
|
||||||
|
display: 'block',
|
||||||
|
backdropFilter: 'blur(4px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Logo: {
|
||||||
|
Wrapper: styled(Flex, {
|
||||||
|
gridArea: 'logo',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,20 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { useMediaQuery } from '@/hooks';
|
||||||
|
|
||||||
import { Button } from '@/components/core';
|
|
||||||
import { Logo } from '@/components/logo/logo';
|
|
||||||
|
|
||||||
import { ConnectWalletButton } from './connect-wallet-button';
|
import { ConnectWalletButton } from './connect-wallet-button';
|
||||||
|
import { Logo } from './logo';
|
||||||
import { NavBarStyles as Styles } from './nav-bar.styles';
|
import { NavBarStyles as Styles } from './nav-bar.styles';
|
||||||
|
import { Navigation } from './navigation';
|
||||||
|
import { Sidebar } from './sidebar';
|
||||||
|
|
||||||
export const NavBar: React.FC = () => {
|
export const NavBar: React.FC = () => {
|
||||||
|
const enableSidebar = useMediaQuery('(max-width: 540px)');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styles.Container>
|
<Styles.Container>
|
||||||
<Styles.Content>
|
<Styles.Content>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
|
||||||
<Styles.Navigation>
|
|
||||||
<Button as={Link} to="/explore" variant="link" color="gray">
|
|
||||||
Explore
|
|
||||||
</Button>
|
|
||||||
<Button as={Link} to="/mint" variant="link" color="gray">
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
<Button as={Link} to="/" variant="link" color="gray">
|
|
||||||
Learn
|
|
||||||
</Button>
|
|
||||||
</Styles.Navigation>
|
|
||||||
<ConnectWalletButton />
|
<ConnectWalletButton />
|
||||||
|
{enableSidebar ? <Sidebar /> : <Navigation />}
|
||||||
</Styles.Content>
|
</Styles.Content>
|
||||||
</Styles.Container>
|
</Styles.Container>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { forwardStyledRef } from '@/theme';
|
||||||
|
|
||||||
|
import { NavBarStyles as S } from './nav-bar.styles';
|
||||||
|
|
||||||
|
const Paths = [
|
||||||
|
{ path: '/explore', name: 'Explore', activeRegex: /\/$|\/explore/ },
|
||||||
|
{ path: '/mint', name: 'Create', activeRegex: /\/mint/ },
|
||||||
|
{ path: '/', name: 'Learn', activeRegex: /\/learn/ },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Navigation = forwardStyledRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentPropsWithRef<typeof S.Navigation.Container>
|
||||||
|
>((props, ref) => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<S.Navigation.Container {...props} ref={ref}>
|
||||||
|
{Paths.map(({ path, name, activeRegex }) => (
|
||||||
|
<S.Navigation.Button
|
||||||
|
key={path}
|
||||||
|
as={Link}
|
||||||
|
to={path}
|
||||||
|
active={activeRegex.test(location.pathname)}
|
||||||
|
variant="link"
|
||||||
|
color="gray"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</S.Navigation.Button>
|
||||||
|
))}
|
||||||
|
</S.Navigation.Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { Button, Icon } from '@/components/core';
|
||||||
|
|
||||||
|
import { NavBarStyles as Styles } from './nav-bar.styles';
|
||||||
|
import { Navigation } from './navigation';
|
||||||
|
|
||||||
|
export const Sidebar: React.FC = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleToggle = (): void => setIsOpen(!isOpen);
|
||||||
|
|
||||||
|
const handleNavigationClick = (): void => setIsOpen(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
const { current } = sidebarRef;
|
||||||
|
if (!current) return;
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent): void => {
|
||||||
|
if (current && !current.contains(event.target as Node)) setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}, [isOpen, sidebarRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={handleToggle}
|
||||||
|
css={{ gridArea: 'menu', fontSize: '$lg' }}
|
||||||
|
>
|
||||||
|
<Icon name="menu" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Styles.Sidebar.Backdrop open={isOpen} />
|
||||||
|
|
||||||
|
<Styles.Sidebar.Content open={isOpen} ref={sidebarRef}>
|
||||||
|
<Navigation stacked onClick={handleNavigationClick} />
|
||||||
|
</Styles.Sidebar.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './logo';
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { styled } from '@/theme';
|
|
||||||
|
|
||||||
import { Flex } from '../layout';
|
|
||||||
|
|
||||||
export abstract class LogoStyles {
|
|
||||||
static readonly Container = styled(Flex, {
|
|
||||||
cursor: 'pointer',
|
|
||||||
flex: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
static readonly Logo = styled('img', {
|
|
||||||
width: '$6',
|
|
||||||
height: 'auto',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Icon } from '../core/icon';
|
|
||||||
import { LogoStyles as LS } from './logo.styles';
|
|
||||||
|
|
||||||
export const Logo: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<LS.Container onClick={() => navigate('/home')}>
|
|
||||||
<Icon
|
|
||||||
name="fleekLogo"
|
|
||||||
css={{ fontSize: '$2xl' }}
|
|
||||||
iconElementCss={{ height: '$6' }}
|
|
||||||
/>
|
|
||||||
<Icon name="fleekName" css={{ fontSize: '$6xl', mr: '$3' }} />
|
|
||||||
<Icon name="betaTag" css={{ fontSize: '$5xl' }} />
|
|
||||||
</LS.Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './use-transaction-cost';
|
export * from './use-transaction-cost';
|
||||||
export * from './use-window-scroll-end';
|
export * from './use-window-scroll-end';
|
||||||
export * from './use-debounce';
|
export * from './use-debounce';
|
||||||
|
export * from './use-media-query';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useMediaQuery = (query: string): boolean => {
|
||||||
|
const getMatches = (query: string): boolean => {
|
||||||
|
// Prevents SSR issues
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return window.matchMedia(query).matches;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||||
|
|
||||||
|
const handleChange = (): void => {
|
||||||
|
setMatches(getMatches(query));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const matchMedia = window.matchMedia(query);
|
||||||
|
|
||||||
|
// Triggered at the first client-side load and if query changes
|
||||||
|
handleChange();
|
||||||
|
|
||||||
|
// Listen matchMedia
|
||||||
|
if (matchMedia.addListener) {
|
||||||
|
matchMedia.addListener(handleChange);
|
||||||
|
} else {
|
||||||
|
matchMedia.addEventListener('change', handleChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (matchMedia.removeListener) {
|
||||||
|
matchMedia.removeListener(handleChange);
|
||||||
|
} else {
|
||||||
|
matchMedia.removeEventListener('change', handleChange);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { config, theme } from './themes';
|
||||||
|
|
||||||
|
config.theme.colors;
|
||||||
|
export const alphaColor = (
|
||||||
|
color: keyof typeof theme.colors,
|
||||||
|
value: number
|
||||||
|
): string =>
|
||||||
|
config.theme.colors[color] +
|
||||||
|
`00${Math.round(0xff * value).toString(16)}`.slice(-2);
|
||||||
|
|
@ -23,6 +23,7 @@ export const colors = {
|
||||||
...amber,
|
...amber,
|
||||||
};
|
};
|
||||||
export const darkColors = {
|
export const darkColors = {
|
||||||
|
black: '#000000',
|
||||||
...grayDark,
|
...grayDark,
|
||||||
...slateDark,
|
...slateDark,
|
||||||
...blueDark,
|
...blueDark,
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ export * from './themes';
|
||||||
export * from './foundations';
|
export * from './foundations';
|
||||||
export * from './key-frames';
|
export * from './key-frames';
|
||||||
export * from './forward-styled-ref';
|
export * from './forward-styled-ref';
|
||||||
|
export * from './alpha-color';
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ const createDripStitches = <
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
black: '#000000',
|
|
||||||
...darkColors, // TODO: replace with light colors once it's done the light mode
|
...darkColors, // TODO: replace with light colors once it's done the light mode
|
||||||
...(theme?.colors || {}),
|
...(theme?.colors || {}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue