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:
Felipe Mendes 2023-04-26 16:31:39 -03:00 committed by GitHub
parent cbe8b93594
commit ae8ba51c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 280 additions and 71 deletions

View File

@ -11,7 +11,10 @@
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap"
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
name="description"
content="Minimal UI for Sites as NFTs. Fleek XYZ"

View File

@ -4,6 +4,7 @@ import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter';
import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch';
import { BiSearch } from '@react-icons/all-files/bi/BiSearch';
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 { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt';
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
@ -26,7 +27,7 @@ import {
export const IconLibrary = Object.freeze({
back: IoArrowBackCircleSharp,
betaTag: BetaTag,
'beta-tag': BetaTag,
branch: BiGitBranch,
check: AiOutlineCheck,
'check-circle': IoCheckmarkCircleSharp,
@ -36,10 +37,11 @@ export const IconLibrary = Object.freeze({
error: ErrorIcon,
ethereum: EthereumIcon,
'external-link': FaExternalLinkAlt,
fleekLogo: FleekLogo,
fleekName: FleekName,
'fleek-logo': FleekLogo,
'fleek-name': FleekName,
github: IoLogoGithub,
info: IoInformationCircleSharp,
menu: FaBars,
metamask: MetamaskIcon, //remove if not used
search: BiSearch,
square: BsFillSquareFill,

View File

@ -25,7 +25,7 @@ export const ConnectWalletButton: React.FC = () => {
if (ensName && address) setEnsNameStore(ensName, address);
return (
<Button onClick={show}>
<Button onClick={show} css={{ gridArea: 'wallet' }}>
{isConnected && !!address && !!truncatedAddress ? (
<Flex css={{ gap: '$2' }}>
<Avatar address={address} size={20} />

View File

@ -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>
);
};

View File

@ -1,33 +1,129 @@
import { styled } from '@/theme';
import { StyledButton } from '@/components/core';
import { alphaColor, styled } from '@/theme';
import { Flex } from '../flex.styles';
export abstract class NavBarStyles {
static readonly Container = styled('header', {
export const NavBarStyles = {
Container: styled('header', {
position: 'sticky',
top: 0,
left: 0,
right: 0,
display: 'flex',
alignItems: 'center',
backgroundColor: '$black',
zIndex: '$sticky',
height: '$22',
overflow: 'hidden', // TODO: this must be worked on for responsive layout
});
static readonly Content = styled('div', {
display: 'flex',
'&:after': {
content: '""',
position: 'absolute',
inset: 0,
backgroundColor: alphaColor('black', 0.8),
backdropFilter: 'blur(4px)',
zIndex: -1,
},
}),
Content: styled('div', {
width: '100%',
maxWidth: '$7xl',
margin: '0 auto',
alignItems: 'center',
padding: '$6',
});
gap: '$3',
static readonly Navigation = styled(Flex, {
gap: '$10',
flexGrow: 4,
justifyContent: 'center',
});
}
display: 'grid',
gridTemplateAreas: '"logo navigation wallet menu"',
gridTemplateColumns: 'auto 1fr auto',
}),
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',
}),
},
};

View File

@ -1,29 +1,20 @@
import { Link } from 'react-router-dom';
import { Button } from '@/components/core';
import { Logo } from '@/components/logo/logo';
import { useMediaQuery } from '@/hooks';
import { ConnectWalletButton } from './connect-wallet-button';
import { Logo } from './logo';
import { NavBarStyles as Styles } from './nav-bar.styles';
import { Navigation } from './navigation';
import { Sidebar } from './sidebar';
export const NavBar: React.FC = () => {
const enableSidebar = useMediaQuery('(max-width: 540px)');
return (
<Styles.Container>
<Styles.Content>
<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 />
{enableSidebar ? <Sidebar /> : <Navigation />}
</Styles.Content>
</Styles.Container>
);

View File

@ -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>
);
});

View File

@ -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>
</>
);
};

View File

@ -1 +0,0 @@
export * from './logo';

View File

@ -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',
});
}

View File

@ -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>
);
};

View File

@ -1,3 +1,4 @@
export * from './use-transaction-cost';
export * from './use-window-scroll-end';
export * from './use-debounce';
export * from './use-media-query';

View File

@ -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;
};

View File

@ -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);

View File

@ -23,6 +23,7 @@ export const colors = {
...amber,
};
export const darkColors = {
black: '#000000',
...grayDark,
...slateDark,
...blueDark,

View File

@ -2,3 +2,4 @@ export * from './themes';
export * from './foundations';
export * from './key-frames';
export * from './forward-styled-ref';
export * from './alpha-color';

View File

@ -59,7 +59,6 @@ const createDripStitches = <
},
theme: {
colors: {
black: '#000000',
...darkColors, // TODO: replace with light colors once it's done the light mode
...(theme?.colors || {}),
},