Refactoring components

This commit is contained in:
Hazem Krimi
2023-03-19 18:21:12 +01:00
parent b82f90db48
commit 3a51b7fdb2
26 changed files with 171 additions and 158 deletions
+2
View File
@@ -0,0 +1,2 @@
NEXT_PUBLIC_FORMSPREE_KEY=FORMSPREE_KEY
NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY=GOOGLE_ANALYTICS_KEY
+3 -13
View File
@@ -1,18 +1,8 @@
import { FC } from 'react';
import { StyledCard } from './styles';
import Image from 'next/image'; import Image from 'next/image';
import { StyledCard } from './styles';
import { Props } from './types';
interface Props { const Card = ({ title, description, image, tags, href, target, onClick }: Props) => {
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
const Card: FC<Props> = ({ title, description, image, tags, href, target, onClick }) => {
return ( return (
<StyledCard href={href} onClick={onClick} image={image ? Boolean(image) : undefined} target={target}> <StyledCard href={href} onClick={onClick} image={image ? Boolean(image) : undefined} target={target}>
<div className='card-content'> <div className='card-content'>
+9
View File
@@ -0,0 +1,9 @@
export interface Props {
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { FC } from 'react';
import Highlight, { defaultProps, Language } from 'prism-react-renderer'; import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import theme from 'prism-react-renderer/themes/vsDark'; import theme from 'prism-react-renderer/themes/vsDark';
import { Props } from './types';
import { Line, LineContent, LineNo, Pre } from './styles'; import { Line, LineContent, LineNo, Pre } from './styles';
const CodeBlock: FC<{ className: string, children: React.ReactNode }> = ({ children, className }) => { const CodeBlock = ({ children, className }: Props) => {
const language = className.replace(/language-/, '') as Language; const language = className.replace(/language-/, '') as Language;
return ( return (
+4
View File
@@ -0,0 +1,4 @@
export interface Props {
className: string;
children: React.ReactNode;
}
+3 -3
View File
@@ -1,9 +1,9 @@
import { FC, useContext } from 'react'; import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme'; import { ThemeContext } from '../../styles/theme';
import { StyledFooter } from './styles'; import { StyledFooter } from './styles';
import IconButton from '../../components/IconButton'; import IconButton from '../IconButton';
const Footer: FC = () => { const Footer = () => {
const { mode } = useContext(ThemeContext); const { mode } = useContext(ThemeContext);
return ( return (
+1 -2
View File
@@ -1,8 +1,7 @@
import { FC } from 'react';
import { Wrapper } from './styles'; import { Wrapper } from './styles';
import Image from 'next/image'; import Image from 'next/image';
const Hero: FC = () => ( const Hero = () => (
<Wrapper> <Wrapper>
<div className='intro'> <div className='intro'>
<h2>Hi, I am Hazem</h2> <h2>Hi, I am Hazem</h2>
-45
View File
@@ -1,45 +0,0 @@
import { FC } from 'react';
import styled from 'styled-components';
import Image from 'next/image';
import Link from 'next/link';
interface Props {
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
const Btn = styled(Link)`
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
`;
const IconButton: FC<Props & { className?: string }> = ({
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24
}) => {
return !href ? (
<Btn href='#' onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</Btn>
) : (
<Btn href={href} target={target} onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</Btn>
);
};
export default IconButton;
+24
View File
@@ -0,0 +1,24 @@
import Image from 'next/image';
import { Props } from './types';
import { StyledButton, StyledLink } from './styles';
const IconButton = ({
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24
}: Props) => href ? (
<StyledLink href={href} target={target} onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledLink>
) : (
<StyledButton onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledButton>
);
export default IconButton;
+18
View File
@@ -0,0 +1,18 @@
import styled, { css } from 'styled-components';
import Link from 'next/link';
const sharedStyles = css`
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
`;
export const StyledLink = styled(Link)`
${sharedStyles}
`;
export const StyledButton = styled.button`
${sharedStyles}
`;
+10
View File
@@ -0,0 +1,10 @@
export interface Props {
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
className?: string;
}
-62
View File
@@ -1,62 +0,0 @@
import React, { FC } from 'react';
import styled from 'styled-components';
interface Props {
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
}
const SmallField = styled.input`
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
const BigField = styled.textarea`
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
const Input: FC<Props & { className?: string }> = ({
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange
}) => {
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
};
export default Input;
+37
View File
@@ -0,0 +1,37 @@
import { BigField, SmallField } from "./styles";
import { Props } from "./types";
const Input = ({
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange
}: Props) => {
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
};
export default Input;
+16
View File
@@ -0,0 +1,16 @@
import styled from "styled-components";
export const SmallField = styled.input`
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
export const BigField = styled.textarea`
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
+10
View File
@@ -0,0 +1,10 @@
export interface Props {
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
className?: string;
}
+5 -5
View File
@@ -1,7 +1,7 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme'; import { ThemeContext } from '../../styles/theme';
import { Props } from './types'; import { Props } from './types';
import { Btn } from './styles'; import { StyledLink, StyledButton } from './styles';
const MDXButton = ({ const MDXButton = ({
variant = 'text', variant = 'text',
@@ -15,7 +15,7 @@ const MDXButton = ({
const { mode } = useContext(ThemeContext); const { mode } = useContext(ThemeContext);
return link ? ( return link ? (
<Btn <StyledLink
href={link} href={link}
target={target} target={target}
variant={variant} variant={variant}
@@ -25,11 +25,11 @@ const MDXButton = ({
className={className} className={className}
> >
{children} {children}
</Btn> </StyledLink>
) : ( ) : (
<Btn href="#" variant={variant} type={type} mode={mode} disabled={disabled} className={className}> <StyledButton variant={variant} type={type} mode={mode} disabled={disabled} className={className}>
{children} {children}
</Btn> </StyledButton>
); );
}; };
+11 -6
View File
@@ -1,13 +1,12 @@
import Link from 'next/link'; import Link from 'next/link';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import { Props } from './types'; import { Props } from './types';
export const Btn = styled(Link)<Props>` export const sharedStyles = css<Props>`
cursor: pointer; cursor: pointer;
display: ${({ variant }) => display: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'block' : 'inline'}; ['action', 'outline'].includes(variant as string) ? 'block' : 'inline'};
width: ${({ variant }) => (['action', 'outline'].includes(variant as string) ? '100%' : 'auto')}; width: ${({ variant }) => (['action', 'outline'].includes(variant as string) ? '100%' : 'auto')};
/* TODO: fix theme blue color problem */
background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')}; background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')};
color: ${({ variant, mode }) => color: ${({ variant, mode }) =>
variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'}; variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'};
@@ -25,9 +24,7 @@ export const Btn = styled(Link)<Props>`
text-decoration: none; text-decoration: none;
transition: color 250ms ease-in-out; transition: color 250ms ease-in-out;
${({ disabled }) => ${({ disabled }) => disabled && `
disabled &&
`
background: gray; background: gray;
cursor: default; cursor: default;
`} `}
@@ -37,3 +34,11 @@ export const Btn = styled(Link)<Props>`
['action', 'outline'].includes(variant as string) ? '.5rem .75rem' : '0rem'}; ['action', 'outline'].includes(variant as string) ? '.5rem .75rem' : '0rem'};
} }
`; `;
export const StyledLink = styled(Link)<Props>`
${sharedStyles}
`;
export const StyledButton = styled.button<Omit<Props, 'href'>>`
${sharedStyles}
`;
+3 -3
View File
@@ -1,11 +1,11 @@
import { FC, useContext, useRef, useEffect } from 'react'; import { useContext, useRef, useEffect } from 'react';
import { ThemeContext } from '../../styles/theme'; import { ThemeContext } from '../../styles/theme';
import { Props } from './types'; import { Props } from './types';
import { Bar } from './styles'; import { Bar } from './styles';
import IconButton from '../IconButton'; import IconButton from '../IconButton';
import Button from '../Button'; import Button from '../Button';
const MobileNav: FC<Props> = ({ open, close }) => { const MobileNav = ({ open, close }: Props) => {
const { mode, toggle } = useContext(ThemeContext); const { mode, toggle } = useContext(ThemeContext);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@@ -45,7 +45,7 @@ const MobileNav: FC<Props> = ({ open, close }) => {
close(); close();
}} }}
> >
{mode === 'dark' ? 'Light Mode' : 'Dark Mode'} {mode === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</Button> </Button>
</div> </div>
<div className='mobile-button-wrapper'> <div className='mobile-button-wrapper'>
+3 -3
View File
@@ -1,4 +1,4 @@
import { FC, useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { ThemeContext } from '../../styles/theme'; import { ThemeContext } from '../../styles/theme';
import { Bar } from './styles'; import { Bar } from './styles';
import Link from 'next/link'; import Link from 'next/link';
@@ -7,7 +7,7 @@ import Button from '../Button';
import IconButton from '../IconButton'; import IconButton from '../IconButton';
import MobileNav from '../MobileNav'; import MobileNav from '../MobileNav';
const Nav: FC = () => { const Nav = () => {
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false); const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { mode, toggle } = useContext(ThemeContext); const { mode, toggle } = useContext(ThemeContext);
@@ -41,7 +41,7 @@ const Nav: FC = () => {
Resume Resume
</Button> </Button>
<IconButton <IconButton
alt='Theme toggler' alt='Hamburger menu'
icon={mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'} icon={mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'}
onClick={() => setMobileNavOpen(true)} onClick={() => setMobileNavOpen(true)}
/> />
+1 -2
View File
@@ -1,10 +1,9 @@
import { FC } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Head from 'next/head'; import Head from 'next/head';
import IconButton from '../components/IconButton'; import IconButton from '../components/IconButton';
import { Wrapper } from '../styles/404'; import { Wrapper } from '../styles/404';
const NotFound: FC = () => { const NotFound = () => {
const router = useRouter(); const router = useRouter();
return ( return (
+2 -2
View File
@@ -1,4 +1,4 @@
import { FC, useEffect } from 'react'; import { useEffect } from 'react';
import { getBlogPostsSlugs, getBlogPostdata } from '../../utils/blog'; import { getBlogPostsSlugs, getBlogPostdata } from '../../utils/blog';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'; import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
@@ -19,7 +19,7 @@ interface Props {
text: string; text: string;
} }
const BlogPost: FC<Props> = ({ source, frontMatter, text }) => { const BlogPost = ({ source, frontMatter, text }: Props) => {
const router = useRouter(); const router = useRouter();
const stats = readingTime(text); const stats = readingTime(text);
const htmlOverrides = { code: CodeBlock }; const htmlOverrides = { code: CodeBlock };
+1 -2
View File
@@ -1,4 +1,3 @@
import React, { FC } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getBlogPosts } from '../../utils/blog'; import { getBlogPosts } from '../../utils/blog';
import { Wrapper } from '../../styles/blog'; import { Wrapper } from '../../styles/blog';
@@ -18,7 +17,7 @@ interface Props {
}[]; }[];
} }
const Index: FC<Props> = ({ blogPosts }) => { const Index = ({ blogPosts }: Props) => {
const router = useRouter(); const router = useRouter();
return ( return (
+2 -2
View File
@@ -1,11 +1,11 @@
import React, { FC, useState } from 'react'; import { useState } from 'react';
import { useForm, ValidationError } from '@formspree/react'; import { useForm, ValidationError } from '@formspree/react';
import Head from 'next/head'; import Head from 'next/head';
import { Wrapper } from '../styles/contact'; import { Wrapper } from '../styles/contact';
import Input from '../components/Input'; import Input from '../components/Input';
import MDXButton from '../components/MDXButton'; import MDXButton from '../components/MDXButton';
const About: FC = () => { const About = () => {
const [form, setForm] = useState<{ name: string; email: string; message: string }>({ const [form, setForm] = useState<{ name: string; email: string; message: string }>({
name: '', name: '',
email: '', email: '',
+1 -2
View File
@@ -1,4 +1,3 @@
import { FC } from 'react';
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';
@@ -26,7 +25,7 @@ interface Props {
}[]; }[];
} }
const Index: FC<Props> = ({ blogPosts, portfolioProjects }) => { const Index = ({ blogPosts, portfolioProjects }: Props) => {
return ( return (
<> <>
<Head> <Head>
+2 -2
View File
@@ -1,4 +1,4 @@
import { FC, useEffect } from 'react'; import { useEffect } from 'react';
import { getPortfolioPorjectsSlugs, getPortfolioProjectdata } from '../../utils/portfolio'; import { getPortfolioPorjectsSlugs, getPortfolioProjectdata } from '../../utils/portfolio';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'; import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
@@ -18,7 +18,7 @@ interface Props {
frontMatter: any; frontMatter: any;
} }
const PortfolioProject: FC<Props> = ({ source, frontMatter }) => { const PortfolioProject = ({ source, frontMatter }: Props) => {
const router = useRouter(); const router = useRouter();
const htmlOverrides = { code: CodeBlock }; const htmlOverrides = { code: CodeBlock };
const mdxComponents = { Button: MDXButton }; const mdxComponents = { Button: MDXButton };
+1 -2
View File
@@ -1,4 +1,3 @@
import React, { FC } from 'react';
import { getPortfolioProjects } from '../../utils/portfolio'; import { getPortfolioProjects } from '../../utils/portfolio';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Wrapper } from '../../styles/portfolio'; import { Wrapper } from '../../styles/portfolio';
@@ -17,7 +16,7 @@ interface Props {
}[]; }[];
} }
const Index: FC<Props> = ({ portfolioProjects }) => { const Index = ({ portfolioProjects }: Props) => {
const router = useRouter(); const router = useRouter();
return ( return (