Update button behavior to work as link

This commit is contained in:
Hazem Krimi
2021-12-12 14:56:22 +01:00
parent b2453ae6dd
commit 08dc652d25
14 changed files with 1261 additions and 1100 deletions
+2 -2
View File
@@ -19,10 +19,10 @@ This is a project that I made as a step in the interview process for my final ye
# Showcase # Showcase
<Button variant='action' link='https://hazemkrimi.github.io/react-weather-app'> <Button variant='action' link='https://hazemkrimi.github.io/react-weather-app' target='_blank'>
Demo Demo
</Button> </Button>
<Button variant='outline' link='https://github.com/hazemKrimi/react-weather-app'> <Button variant='outline' link='https://github.com/hazemKrimi/react-weather-app' target='_blank'>
Source Code Source Code
</Button> </Button>
+23 -5
View File
@@ -1,13 +1,17 @@
import { FC, useContext } from 'react'; import { FC, useContext } from 'react';
import { DarkModeContext } from '../components/DarkMode'; import { DarkModeContext } from '../components/DarkMode';
import styled from 'styled-components'; import styled from 'styled-components';
import Link from 'next/link';
interface Props { interface Props {
variant?: 'outline' | 'text'; variant?: 'outline' | 'text';
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void; onClick?: () => void;
dark?: boolean; dark?: boolean;
} }
const Btn = styled.button<Props>` const Btn = styled.button<Omit<Props, 'href'>>`
position: relative; position: relative;
display: inline; display: inline;
cursor: pointer; cursor: pointer;
@@ -19,6 +23,7 @@ const Btn = styled.button<Props>`
text-transform: ${({ variant }) => (variant === 'outline' ? 'uppercase' : 'inherit')}; text-transform: ${({ variant }) => (variant === 'outline' ? 'uppercase' : 'inherit')};
padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')}; padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')};
text-align: left; text-align: left;
text-decoration: none;
transition: color 250ms ease-in-out; transition: color 250ms ease-in-out;
z-index: 1; z-index: 1;
@@ -41,7 +46,9 @@ const Btn = styled.button<Props>`
} }
&:hover { &:hover {
color: ${({ variant }) => (variant === 'outline' ? 'var(--background)' : 'inherit')}; @media (min-width: 768px) {
color: ${({ variant }) => (variant === 'outline' ? 'var(--background)' : 'inherit')};
}
} }
&:hover::before { &:hover::before {
@@ -51,6 +58,8 @@ const Btn = styled.button<Props>`
const Button: FC<Props & { className?: string }> = ({ const Button: FC<Props & { className?: string }> = ({
variant = 'text', variant = 'text',
href,
target,
onClick, onClick,
children, children,
className className
@@ -58,9 +67,18 @@ const Button: FC<Props & { className?: string }> = ({
const { dark } = useContext(DarkModeContext); const { dark } = useContext(DarkModeContext);
return ( return (
<Btn variant={variant} dark={dark} onClick={onClick} className={className}> <Link href={href} passHref>
{children} <Btn
</Btn> as='a'
target={target}
variant={variant}
dark={dark}
onClick={onClick}
className={className}
>
{children}
</Btn>
</Link>
); );
}; };
+25 -18
View File
@@ -2,12 +2,15 @@ import { FC, useContext } from 'react';
import { DarkModeContext } from '../components/DarkMode'; import { DarkModeContext } from '../components/DarkMode';
import styled from 'styled-components'; import styled from 'styled-components';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link';
interface Props { interface Props {
title: string; title: string;
description: string; description: string;
image?: string; image?: string;
tags?: string[]; tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void; onClick?: () => void;
} }
@@ -18,6 +21,8 @@ const StyledCard = styled.div<{ dark: boolean; image: boolean }>`
grid-template-columns: auto 150px; grid-template-columns: auto 150px;
align-items: stretch; align-items: stretch;
transition: color 0ms ease-in-out; transition: color 0ms ease-in-out;
text-decoration: none;
color: var(--text);
&:hover { &:hover {
& > div { & > div {
@@ -67,28 +72,30 @@ const StyledCard = styled.div<{ dark: boolean; image: boolean }>`
} }
`; `;
const Card: FC<Props> = ({ title, description, image, tags, onClick }) => { const Card: FC<Props> = ({ title, description, image, tags, href, target, onClick }) => {
const { dark } = useContext(DarkModeContext); const { dark } = useContext(DarkModeContext);
return ( return (
<StyledCard dark={dark} onClick={onClick} image={!!image}> <Link href={href} passHref>
<div> <StyledCard as='a' target={target} dark={dark} onClick={onClick} image={!!image}>
<h3>{title}</h3> <div>
<p>{description}</p> <h3>{title}</h3>
{tags && ( <p>{description}</p>
<div className='tags-wrapper'> {tags && (
{tags.map((tag, index) => ( <div className='tags-wrapper'>
<span key={index}>#{tag}&nbsp;</span> {tags.map((tag, index) => (
))} <span key={index}>#{tag}&nbsp;</span>
</div> ))}
</div>
)}
</div>
{image ? (
<Image src={image} width='100%' height='100%' layout='responsive' />
) : (
<Image src='/no-image.png' width='100%' height='100%' layout='responsive' />
)} )}
</div> </StyledCard>
{image ? ( </Link>
<Image src={image} width='100%' height='100%' layout='responsive' />
) : (
<Image src='/no-image.png' width='100%' height='100%' layout='responsive' />
)}
</StyledCard>
); );
}; };
+10 -5
View File
@@ -53,31 +53,36 @@ const Footer: FC = () => {
icon={dark ? '/icons/light-github.svg' : '/icons/dark-github.svg'} icon={dark ? '/icons/light-github.svg' : '/icons/dark-github.svg'}
width={16} width={16}
height={16} height={16}
onClick={() => window.open('https://github.com/hazemKrimi', '_blank')} href='https://github.com/hazemKrimi'
target='_blank'
/> />
<IconButton <IconButton
icon={dark ? '/icons/light-twitter.svg' : '/icons/dark-twitter.svg'} icon={dark ? '/icons/light-twitter.svg' : '/icons/dark-twitter.svg'}
width={16} width={16}
height={16} height={16}
onClick={() => window.open('https://twitter.com/HazemKrimi', '_blank')} href='https://twitter.com/HazemKrimi'
target='_blank'
/> />
<IconButton <IconButton
icon={dark ? '/icons/light-linkedin.svg' : '/icons/dark-linkedin.svg'} icon={dark ? '/icons/light-linkedin.svg' : '/icons/dark-linkedin.svg'}
width={16} width={16}
height={16} height={16}
onClick={() => window.open('https://linkedin.com/in/hazemkrimi', '_blank')} href='https://linkedin.com/in/hazemkrimi'
target='_blank'
/> />
<IconButton <IconButton
icon={dark ? '/icons/light-codepen.svg' : '/icons/dark-codepen.svg'} icon={dark ? '/icons/light-codepen.svg' : '/icons/dark-codepen.svg'}
width={16} width={16}
height={16} height={16}
onClick={() => window.open('https://codepen.io/hazemkrimi', '_blank')} href='https://codepen.io/hazemkrimi'
target='_blank'
/> />
<IconButton <IconButton
icon={dark ? '/icons/light-dribbble.svg' : '/icons/dark-dribbble.svg'} icon={dark ? '/icons/light-dribbble.svg' : '/icons/dark-dribbble.svg'}
width={16} width={16}
height={16} height={16}
onClick={() => window.open('https://dribbble.com/HazemKrimi', '_blank')} href='https://dribbble.com/HazemKrimi'
target='_blank'
/> />
</div> </div>
<p>Hazem Krimi &copy; {new Date().getFullYear()}</p> <p>Hazem Krimi &copy; {new Date().getFullYear()}</p>
-1
View File
@@ -55,7 +55,6 @@ const Hero: FC = () => {
<h2>Hi, I am Hazem</h2> <h2>Hi, I am Hazem</h2>
<h2>I Like Building Things</h2> <h2>I Like Building Things</h2>
<h2 className='blue'>Software Developer</h2> <h2 className='blue'>Software Developer</h2>
<h2 className='blue'>Hard Working</h2>
<h2 className='blue'>Life Long Learner</h2> <h2 className='blue'>Life Long Learner</h2>
</div> </div>
<div className='illustration'> <div className='illustration'>
+13 -1
View File
@@ -1,10 +1,14 @@
import { FC } from 'react'; import { FC } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link';
interface Props { interface Props {
icon: string; icon: string;
width?: number; width?: number;
height?: number; height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void; onClick?: () => void;
} }
@@ -18,15 +22,23 @@ const Btn = styled.button`
const IconButton: FC<Props & { className?: string }> = ({ const IconButton: FC<Props & { className?: string }> = ({
icon, icon,
href,
target,
onClick, onClick,
className, className,
width = 24, width = 24,
height = 24 height = 24
}) => { }) => {
return ( return !href ? (
<Btn onClick={onClick} className={className}> <Btn onClick={onClick} className={className}>
<Image src={icon} width={width} height={height} /> <Image src={icon} width={width} height={height} />
</Btn> </Btn>
) : (
<Link href={href} passHref>
<Btn as='a' target={target} onClick={onClick} className={className}>
<Image src={icon} width={width} height={height} />
</Btn>
</Link>
); );
}; };
+20 -11
View File
@@ -1,11 +1,13 @@
import { FC, useContext } from 'react'; import { FC, useContext } from 'react';
import { DarkModeContext } from '../components/DarkMode'; import { DarkModeContext } from '../components/DarkMode';
import Link from 'next/link';
import styled from 'styled-components'; import styled from 'styled-components';
interface Props { interface Props {
variant?: 'outline' | 'text' | 'action'; variant?: 'outline' | 'text' | 'action';
type?: 'button' | 'submit'; type?: 'button' | 'submit';
link?: string; link?: string;
target?: HTMLAnchorElement['target'];
dark?: boolean; dark?: boolean;
disabled?: boolean; disabled?: boolean;
} }
@@ -29,6 +31,7 @@ const Btn = styled.button<Props>`
['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'}; ['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'};
text-align: ${({ variant }) => text-align: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'center' : 'left'}; ['action', 'outline'].includes(variant as string) ? 'center' : 'left'};
text-decoration: none;
transition: color 250ms ease-in-out; transition: color 250ms ease-in-out;
${({ disabled }) => ${({ disabled }) =>
@@ -48,23 +51,29 @@ const MDXButton: FC<Props & { className?: string }> = ({
variant = 'text', variant = 'text',
type = 'button', type = 'button',
link, link,
target,
children, children,
disabled, disabled,
className className
}) => { }) => {
const { dark } = useContext(DarkModeContext); const { dark } = useContext(DarkModeContext);
return ( return link ? (
<Btn <Link href={link} passHref>
variant={variant} <Btn
type={type} as='a'
dark={dark} target={target}
onClick={() => { variant={variant}
if (link) window.open(link, '_blank'); type={type}
}} dark={dark}
disabled={disabled} disabled={disabled}
className={className} className={className}
> >
{children}
</Btn>
</Link>
) : (
<Btn variant={variant} type={type} dark={dark} disabled={disabled} className={className}>
{children} {children}
</Btn> </Btn>
); );
+8 -29
View File
@@ -1,6 +1,5 @@
import { FC, useContext, useRef, useEffect } from 'react'; import { FC, useContext, useRef, useEffect } from 'react';
import { DarkModeContext } from './DarkMode'; import { DarkModeContext } from './DarkMode';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import IconButton from './IconButton'; import IconButton from './IconButton';
import Button from './Button'; import Button from './Button';
@@ -44,7 +43,7 @@ const Bar = styled.nav<StyledProps>`
display: flex; display: flex;
margin: 0rem 1rem; margin: 0rem 1rem;
button { a {
color: var(--text-inverted) !important; color: var(--text-inverted) !important;
} }
} }
@@ -53,7 +52,6 @@ const Bar = styled.nav<StyledProps>`
const MobileNav: FC<Props> = ({ open, close }) => { const MobileNav: FC<Props> = ({ open, close }) => {
const { dark, toggle } = useContext(DarkModeContext); const { dark, toggle } = useContext(DarkModeContext);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const router = useRouter();
useEffect(() => { useEffect(() => {
document.addEventListener('mousedown', (event: MouseEvent) => { document.addEventListener('mousedown', (event: MouseEvent) => {
@@ -83,37 +81,18 @@ const MobileNav: FC<Props> = ({ open, close }) => {
/> />
</div> </div>
<div className='mobile-button-wrapper'> <div className='mobile-button-wrapper'>
<Button onClick={() => toggle()}>{dark ? 'Light Mode' : 'Dark Mode'}</Button> <Button href='#' onClick={() => toggle()}>
</div> {dark ? 'Light Mode' : 'Dark Mode'}
<div className='mobile-button-wrapper'>
<Button
onClick={() => {
close();
router.push('/about');
}}
>
About
</Button> </Button>
</div> </div>
<div className='mobile-button-wrapper'> <div className='mobile-button-wrapper'>
<Button <Button href='/about'>About</Button>
onClick={() => {
close();
router.push('/portfolio');
}}
>
Portfolio
</Button>
</div> </div>
<div className='mobile-button-wrapper'> <div className='mobile-button-wrapper'>
<Button <Button href='/portfolio'>Portfolio</Button>
onClick={() => { </div>
close(); <div className='mobile-button-wrapper'>
router.push('/blog'); <Button href='/blog'>Blog</Button>
}}
>
Blog
</Button>
</div> </div>
</Bar> </Bar>
); );
+23 -19
View File
@@ -1,7 +1,7 @@
import { FC, useContext, useState } from 'react'; import { FC, useContext, useState } from 'react';
import { DarkModeContext } from './DarkMode'; import { DarkModeContext } from './DarkMode';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import Button from './Button'; import Button from './Button';
import IconButton from './IconButton'; import IconButton from './IconButton';
@@ -26,7 +26,8 @@ const Bar = styled.nav`
} }
} }
div { div,
a.logo {
display: grid; display: grid;
align-items: center; align-items: center;
column-gap: 1rem; column-gap: 1rem;
@@ -36,7 +37,9 @@ const Bar = styled.nav`
} }
} }
.logo { a.logo {
text-decoration: none;
color: var(--text);
cursor: pointer; cursor: pointer;
grid-template-columns: repeat(2, auto); grid-template-columns: repeat(2, auto);
justify-content: flex-start; justify-content: flex-start;
@@ -65,31 +68,32 @@ const Bar = styled.nav`
const Nav: FC = () => { const Nav: FC = () => {
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false); const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { dark, toggle } = useContext(DarkModeContext); const { dark, toggle } = useContext(DarkModeContext);
const router = useRouter();
return ( return (
<Bar> <Bar>
<div className='logo' onClick={() => router.push('/')}> <Link href='/' passHref>
<Image <a className='logo'>
className='logo-image' <Image
src={dark ? '/light-logo.svg' : '/dark-logo.svg'} className='logo-image'
alt='Logo Image' src={dark ? '/light-logo.svg' : '/dark-logo.svg'}
width={48} alt='Logo Image'
height={48} width={48}
/> height={48}
<h1>Hazem Krimi</h1> />
</div> <h1>Hazem Krimi</h1>
</a>
</Link>
<div className='buttons'> <div className='buttons'>
<IconButton icon={dark ? '/icons/sun.svg' : '/icons/moon.svg'} onClick={toggle} /> <IconButton icon={dark ? '/icons/sun.svg' : '/icons/moon.svg'} onClick={toggle} />
<Button onClick={() => router.push('/about')}>About</Button> <Button href='/about'>About</Button>
<Button onClick={() => router.push('/portfolio')}>Portfolio</Button> <Button href='/portfolio'>Portfolio</Button>
<Button onClick={() => router.push('/blog')}>Blog</Button> <Button href='/blog'>Blog</Button>
<Button variant='outline' onClick={() => window.open('/resume.pdf', '_blank')}> <Button href='/resume.pdf' target='_blank' variant='outline'>
Resume Resume
</Button> </Button>
</div> </div>
<div className='mobile-buttons'> <div className='mobile-buttons'>
<Button variant='outline' onClick={() => window.open('/hazem-krimi.pdf', '_blank')}> <Button href='/resume.pdf' target='_blank' variant='outline'>
Resume Resume
</Button> </Button>
<IconButton <IconButton
-1
View File
@@ -11,7 +11,6 @@
"@formspree/react": "^2.2.3", "@formspree/react": "^2.2.3",
"@mdx-js/loader": "^1.6.22", "@mdx-js/loader": "^1.6.22",
"@next/mdx": "^10.0.4", "@next/mdx": "^10.0.4",
"framer-motion": "^4.1.17",
"gray-matter": "^4.0.2", "gray-matter": "^4.0.2",
"mdx-embed": "^0.0.17", "mdx-embed": "^0.0.17",
"next": "^11.1.0", "next": "^11.1.0",
+1 -1
View File
@@ -86,7 +86,7 @@ const Index: FC<Props> = ({ blogPosts }) => {
<div className='articles-wrapper'> <div className='articles-wrapper'>
{blogPosts.length !== 0 ? ( {blogPosts.length !== 0 ? (
blogPosts.map(({ slug, ...rest }) => ( blogPosts.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} onClick={() => router.push(`/blog/${slug}`)} /> <Card {...rest} key={slug} href={`/blog/${slug}`} />
)) ))
) : ( ) : (
<h4>Nothing for now</h4> <h4>Nothing for now</h4>
+4 -9
View File
@@ -1,5 +1,4 @@
import { FC } from 'react'; import { FC } from 'react';
import { useRouter } from 'next/router';
import { getPortfolioProjects } from '../utils/portfolio'; import { getPortfolioProjects } from '../utils/portfolio';
import { getBlogPosts } from '../utils/blog'; import { getBlogPosts } from '../utils/blog';
import { GetStaticProps } from 'next'; import { GetStaticProps } from 'next';
@@ -75,8 +74,6 @@ const Wrapper = styled.div`
`; `;
const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => { const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => {
const router = useRouter();
return ( return (
<> <>
<Head> <Head>
@@ -103,7 +100,7 @@ const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => {
<Hero /> <Hero />
<div className='content'> <div className='content'>
<h1>Portfolio</h1> <h1>Portfolio</h1>
<Button className='blue' onClick={() => router.push('/portfolio')}> <Button href='/portfolio' className='blue'>
See More See More
</Button> </Button>
<div className='portfolio'> <div className='portfolio'>
@@ -112,7 +109,7 @@ const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => {
portfolioProjects portfolioProjects
.slice(0, 3) .slice(0, 3)
.map(({ slug, ...rest }) => ( .map(({ slug, ...rest }) => (
<Card {...rest} key={slug} onClick={() => router.push(`/portfolio/${slug}`)} /> <Card {...rest} key={slug} href={`/portfolio/${slug}`} />
)) ))
) : ( ) : (
<h4>Nothing for now</h4> <h4>Nothing for now</h4>
@@ -120,7 +117,7 @@ const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => {
</div> </div>
</div> </div>
<h1>Blog</h1> <h1>Blog</h1>
<Button className='blue' onClick={() => router.push('/blog')}> <Button href='/blog' className='blue'>
See More See More
</Button> </Button>
<div className='blog'> <div className='blog'>
@@ -128,9 +125,7 @@ const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => {
{blogPosts.length !== 0 ? ( {blogPosts.length !== 0 ? (
blogPosts blogPosts
.slice(0, 3) .slice(0, 3)
.map(({ slug, ...rest }) => ( .map(({ slug, ...rest }) => <Card {...rest} key={slug} href={`/blog/${slug}`} />)
<Card {...rest} key={slug} onClick={() => router.push(`/blog/${slug}`)} />
))
) : ( ) : (
<h4>Nothing for now</h4> <h4>Nothing for now</h4>
)} )}
+1 -1
View File
@@ -85,7 +85,7 @@ const Index: FC<Props> = ({ portfolioProjects }) => {
<div className='projects-wrapper'> <div className='projects-wrapper'>
{portfolioProjects.length !== 0 ? ( {portfolioProjects.length !== 0 ? (
portfolioProjects.map(({ slug, ...rest }) => ( portfolioProjects.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} onClick={() => router.push(`/portfolio/${slug}`)} /> <Card {...rest} key={slug} href={`/portfolio/${slug}`} />
)) ))
) : ( ) : (
<h4>Nothing for now</h4> <h4>Nothing for now</h4>
+1131 -997
View File
File diff suppressed because it is too large Load Diff