style: 迁移到 v2.
Some checks failed
🚀 Build and deploy by ftp / 🎉 Deploy (push) Failing after 8m21s

This commit is contained in:
2023-08-16 23:55:57 +08:00
parent de1da22508
commit 3932a2b612
45 changed files with 806 additions and 606 deletions

View File

@@ -1,13 +1,12 @@
import Image from './Image'
import Link from './Link'
import Image from './Image';
import Link from './Link';
const Card = ({ title, description, imgSrc, href }) => (
<div className="md max-w-[544px] p-4 md:w-1/2">
<div
className={`${
imgSrc && 'h-full'
} overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}
>
} overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}>
{imgSrc &&
(href ? (
<Link href={href} aria-label={`Link to ${title}`}>
@@ -38,19 +37,20 @@ const Card = ({ title, description, imgSrc, href }) => (
title
)}
</h2>
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">
{description}
</p>
{href && (
<Link
href={href}
className="text-base font-medium leading-6 text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
aria-label={`Link to ${title}`}
>
aria-label={`Link to ${title}`}>
Learn more &rarr;
</Link>
)}
</div>
</div>
</div>
)
);
export default Card
export default Card;

View File

@@ -1,17 +1,19 @@
'use client'
'use client';
import { Comments as CommentsComponent } from 'pliny/comments'
import { useState } from 'react'
import siteMetadata from '@/data/siteMetadata'
import { Comments as CommentsComponent } from 'pliny/comments';
import { useState } from 'react';
import siteMetadata from '@/data/siteMetadata';
export default function Comments({ slug }: { slug: string }) {
const [loadComments, setLoadComments] = useState(false)
const [loadComments, setLoadComments] = useState(false);
return (
<>
{!loadComments && <button onClick={() => setLoadComments(true)}>Load Comments</button>}
{!loadComments && (
<button onClick={() => setLoadComments(true)}>Load Comments</button>
)}
{siteMetadata.comments && loadComments && (
<CommentsComponent commentsConfig={siteMetadata.comments} slug={slug} />
)}
</>
)
);
}

View File

@@ -1,13 +1,17 @@
import Link from './Link'
import siteMetadata from '@/data/siteMetadata'
import SocialIcon from '@/components/social-icons'
import Link from './Link';
import siteMetadata from '@/data/siteMetadata';
import SocialIcon from '@/components/social-icons';
export default function Footer() {
return (
<footer>
<div className="mt-16 flex flex-col items-center">
<div className="mb-3 flex space-x-4">
<SocialIcon kind="mail" href={`mailto:${siteMetadata.email}`} size={6} />
<SocialIcon
kind="mail"
href={`mailto:${siteMetadata.email}`}
size={6}
/>
<SocialIcon kind="github" href={siteMetadata.github} size={6} />
<SocialIcon kind="facebook" href={siteMetadata.facebook} size={6} />
<SocialIcon kind="youtube" href={siteMetadata.youtube} size={6} />
@@ -33,5 +37,5 @@ export default function Footer() {
</div>
</div>
</footer>
)
);
}

View File

@@ -1,10 +1,10 @@
import siteMetadata from '@/data/siteMetadata'
import headerNavLinks from '@/data/headerNavLinks'
import Logo from '@/data/logo.svg'
import Link from './Link'
import MobileNav from './MobileNav'
import ThemeSwitch from './ThemeSwitch'
import SearchButton from './SearchButton'
import siteMetadata from '@/data/siteMetadata';
import headerNavLinks from '@/data/headerNavLinks';
import Logo from '@/data/logo.svg';
import Link from './Link';
import MobileNav from './MobileNav';
import ThemeSwitch from './ThemeSwitch';
import SearchButton from './SearchButton';
const Header = () => {
return (
@@ -32,8 +32,7 @@ const Header = () => {
<Link
key={link.title}
href={link.href}
className="hidden sm:block font-medium text-gray-900 dark:text-gray-100"
>
className="hidden sm:block font-medium text-gray-900 dark:text-gray-100">
{link.title}
</Link>
))}
@@ -42,7 +41,7 @@ const Header = () => {
<MobileNav />
</div>
</header>
)
}
);
};
export default Header
export default Header;

View File

@@ -1,5 +1,5 @@
import NextImage, { ImageProps } from 'next/image'
import NextImage, { ImageProps } from 'next/image';
const Image = ({ ...rest }: ImageProps) => <NextImage {...rest} />
const Image = ({ ...rest }: ImageProps) => <NextImage {...rest} />;
export default Image
export default Image;

View File

@@ -1,27 +1,28 @@
import { Inter } from 'next/font/google'
import SectionContainer from './SectionContainer'
import Footer from './Footer'
import { ReactNode } from 'react'
import Header from './Header'
import { Inter } from 'next/font/google';
import SectionContainer from './SectionContainer';
import Footer from './Footer';
import { ReactNode } from 'react';
import Header from './Header';
interface Props {
children: ReactNode
children: ReactNode;
}
const inter = Inter({
subsets: ['latin'],
})
});
const LayoutWrapper = ({ children }: Props) => {
return (
<SectionContainer>
<div className={`${inter.className} flex h-screen flex-col justify-between font-sans`}>
<div
className={`${inter.className} flex h-screen flex-col justify-between font-sans`}>
<Header />
<main className="mb-auto">{children}</main>
<Footer />
</div>
</SectionContainer>
)
}
);
};
export default LayoutWrapper
export default LayoutWrapper;

View File

@@ -1,21 +1,24 @@
/* eslint-disable jsx-a11y/anchor-has-content */
import Link from 'next/link'
import type { LinkProps } from 'next/link'
import { AnchorHTMLAttributes } from 'react'
import Link from 'next/link';
import type { LinkProps } from 'next/link';
import { AnchorHTMLAttributes } from 'react';
const CustomLink = ({ href, ...rest }: LinkProps & AnchorHTMLAttributes<HTMLAnchorElement>) => {
const isInternalLink = href && href.startsWith('/')
const isAnchorLink = href && href.startsWith('#')
const CustomLink = ({
href,
...rest
}: LinkProps & AnchorHTMLAttributes<HTMLAnchorElement>) => {
const isInternalLink = href && href.startsWith('/');
const isAnchorLink = href && href.startsWith('#');
if (isInternalLink) {
return <Link href={href} {...rest} />
return <Link href={href} {...rest} />;
}
if (isAnchorLink) {
return <a href={href} {...rest} />
return <a href={href} {...rest} />;
}
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />
}
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />;
};
export default CustomLink
export default CustomLink;

View File

@@ -1,9 +1,9 @@
import TOCInline from 'pliny/ui/TOCInline'
import Pre from 'pliny/ui/Pre'
import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm'
import type { MDXComponents } from 'mdx/types'
import Image from './Image'
import CustomLink from './Link'
import TOCInline from 'pliny/ui/TOCInline';
import Pre from 'pliny/ui/Pre';
import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm';
import type { MDXComponents } from 'mdx/types';
import Image from './Image';
import CustomLink from './Link';
export const components: MDXComponents = {
Image,
@@ -11,4 +11,4 @@ export const components: MDXComponents = {
a: CustomLink,
pre: Pre,
BlogNewsletterForm,
}
};

View File

@@ -1,33 +1,35 @@
'use client'
'use client';
import { useState } from 'react'
import Link from './Link'
import headerNavLinks from '@/data/headerNavLinks'
import { useState } from 'react';
import Link from './Link';
import headerNavLinks from '@/data/headerNavLinks';
const MobileNav = () => {
const [navShow, setNavShow] = useState(false)
const [navShow, setNavShow] = useState(false);
const onToggleNav = () => {
setNavShow((status) => {
if (status) {
document.body.style.overflow = 'auto'
document.body.style.overflow = 'auto';
} else {
// Prevent scrolling
document.body.style.overflow = 'hidden'
document.body.style.overflow = 'hidden';
}
return !status
})
}
return !status;
});
};
return (
<>
<button aria-label="Toggle Menu" onClick={onToggleNav} className="sm:hidden">
<button
aria-label="Toggle Menu"
onClick={onToggleNav}
className="sm:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100 h-8 w-8"
>
className="text-gray-900 dark:text-gray-100 h-8 w-8">
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
@@ -38,16 +40,17 @@ const MobileNav = () => {
<div
className={`fixed left-0 top-0 z-10 h-full w-full transform opacity-95 dark:opacity-[0.98] bg-white duration-300 ease-in-out dark:bg-gray-950 ${
navShow ? 'translate-x-0' : 'translate-x-full'
}`}
>
}`}>
<div className="flex justify-end">
<button className="mr-8 mt-11 h-8 w-8" aria-label="Toggle Menu" onClick={onToggleNav}>
<button
className="mr-8 mt-11 h-8 w-8"
aria-label="Toggle Menu"
onClick={onToggleNav}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
className="text-gray-900 dark:text-gray-100">
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
@@ -62,8 +65,7 @@ const MobileNav = () => {
<Link
href={link.href}
className="text-2xl font-bold tracking-widest text-gray-900 dark:text-gray-100"
onClick={onToggleNav}
>
onClick={onToggleNav}>
{link.title}
</Link>
</div>
@@ -71,7 +73,7 @@ const MobileNav = () => {
</nav>
</div>
</>
)
}
);
};
export default MobileNav
export default MobileNav;

View File

@@ -1,7 +1,7 @@
import { ReactNode } from 'react'
import { ReactNode } from 'react';
interface Props {
children: ReactNode
children: ReactNode;
}
export default function PageTitle({ children }: Props) {
@@ -9,5 +9,5 @@ export default function PageTitle({ children }: Props) {
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-5xl md:leading-14">
{children}
</h1>
)
);
}

View File

@@ -1,37 +1,37 @@
'use client'
'use client';
import siteMetadata from '@/data/siteMetadata'
import { useEffect, useState } from 'react'
import siteMetadata from '@/data/siteMetadata';
import { useEffect, useState } from 'react';
const ScrollTopAndComment = () => {
const [show, setShow] = useState(false)
const [show, setShow] = useState(false);
useEffect(() => {
const handleWindowScroll = () => {
if (window.scrollY > 50) setShow(true)
else setShow(false)
}
if (window.scrollY > 50) setShow(true);
else setShow(false);
};
window.addEventListener('scroll', handleWindowScroll)
return () => window.removeEventListener('scroll', handleWindowScroll)
}, [])
window.addEventListener('scroll', handleWindowScroll);
return () => window.removeEventListener('scroll', handleWindowScroll);
}, []);
const handleScrollTop = () => {
window.scrollTo({ top: 0 })
}
window.scrollTo({ top: 0 });
};
const handleScrollToComment = () => {
document.getElementById('comment')?.scrollIntoView()
}
document.getElementById('comment')?.scrollIntoView();
};
return (
<div
className={`fixed bottom-8 right-8 hidden flex-col gap-3 ${show ? 'md:flex' : 'md:hidden'}`}
>
className={`fixed bottom-8 right-8 hidden flex-col gap-3 ${
show ? 'md:flex' : 'md:hidden'
}`}>
{siteMetadata.comments?.provider && (
<button
aria-label="Scroll To Comment"
onClick={handleScrollToComment}
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600"
>
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600">
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
@@ -44,8 +44,7 @@ const ScrollTopAndComment = () => {
<button
aria-label="Scroll To Top"
onClick={handleScrollTop}
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600"
>
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600">
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
@@ -55,7 +54,7 @@ const ScrollTopAndComment = () => {
</svg>
</button>
</div>
)
}
);
};
export default ScrollTopAndComment
export default ScrollTopAndComment;

View File

@@ -1,14 +1,15 @@
import { AlgoliaButton } from 'pliny/search/AlgoliaButton'
import { KBarButton } from 'pliny/search/KBarButton'
import siteMetadata from '@/data/siteMetadata'
import { AlgoliaButton } from 'pliny/search/AlgoliaButton';
import { KBarButton } from 'pliny/search/KBarButton';
import siteMetadata from '@/data/siteMetadata';
const SearchButton = () => {
if (
siteMetadata.search &&
(siteMetadata.search.provider === 'algolia' || siteMetadata.search.provider === 'kbar')
(siteMetadata.search.provider === 'algolia' ||
siteMetadata.search.provider === 'kbar')
) {
const SearchButtonWrapper =
siteMetadata.search.provider === 'algolia' ? AlgoliaButton : KBarButton
siteMetadata.search.provider === 'algolia' ? AlgoliaButton : KBarButton;
return (
<SearchButtonWrapper aria-label="Search">
@@ -18,8 +19,7 @@ const SearchButton = () => {
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="text-gray-900 dark:text-gray-100 h-6 w-6"
>
className="text-gray-900 dark:text-gray-100 h-6 w-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -27,8 +27,8 @@ const SearchButton = () => {
/>
</svg>
</SearchButtonWrapper>
)
);
}
}
};
export default SearchButton
export default SearchButton;

View File

@@ -1,11 +1,13 @@
import { ReactNode } from 'react'
import { ReactNode } from 'react';
interface Props {
children: ReactNode
children: ReactNode;
}
export default function SectionContainer({ children }: Props) {
return (
<section className="mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0">{children}</section>
)
<section className="mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0">
{children}
</section>
);
}

View File

@@ -1,18 +1,17 @@
import Link from 'next/link'
import { slug } from 'github-slugger'
import Link from 'next/link';
import { slug } from 'github-slugger';
interface Props {
text: string
text: string;
}
const Tag = ({ text }: Props) => {
return (
<Link
href={`/tags/${slug(text)}`}
className="mr-3 text-sm font-medium uppercase text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
>
className="mr-3 text-sm font-medium uppercase text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
{text.split(' ').join('-')}
</Link>
)
}
);
};
export default Tag
export default Tag;

View File

@@ -1,30 +1,28 @@
'use client'
'use client';
import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react';
import { useTheme } from 'next-themes';
const ThemeSwitch = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// When mounted on client, now we can show the UI
useEffect(() => setMounted(true), [])
useEffect(() => setMounted(true), []);
if (!mounted) {
return null
return null;
}
return (
<button
aria-label="Toggle Dark Mode"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100 h-6 w-6"
>
className="text-gray-900 dark:text-gray-100 h-6 w-6">
{mounted && theme === 'dark' ? (
<path
fillRule="evenodd"
@@ -36,7 +34,7 @@ const ThemeSwitch = () => {
)}
</svg>
</button>
)
}
);
};
export default ThemeSwitch
export default ThemeSwitch;

View File

@@ -1,4 +1,4 @@
import { SVGProps } from 'react'
import { SVGProps } from 'react';
// Icons taken from: https://simpleicons.org/
// To add a new icon, add a new function here and add it to components in social-icons/index.tsx
@@ -8,7 +8,7 @@ export function Facebook(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"></path>
</svg>
)
);
}
export function Github(svgProps: SVGProps<SVGSVGElement>) {
@@ -16,7 +16,7 @@ export function Github(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</svg>
)
);
}
export function Linkedin(svgProps: SVGProps<SVGSVGElement>) {
@@ -24,7 +24,7 @@ export function Linkedin(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path>
</svg>
)
);
}
export function Mail(svgProps: SVGProps<SVGSVGElement>) {
@@ -33,7 +33,7 @@ export function Mail(svgProps: SVGProps<SVGSVGElement>) {
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"></path>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"></path>
</svg>
)
);
}
export function Twitter(svgProps: SVGProps<SVGSVGElement>) {
@@ -41,7 +41,7 @@ export function Twitter(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path>
</svg>
)
);
}
export function Youtube(svgProps: SVGProps<SVGSVGElement>) {
@@ -49,7 +49,7 @@ export function Youtube(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M23.499 6.203a3.008 3.008 0 00-2.089-2.089c-1.87-.501-9.4-.501-9.4-.501s-7.509-.01-9.399.501a3.008 3.008 0 00-2.088 2.09A31.258 31.26 0 000 12.01a31.258 31.26 0 00.523 5.785 3.008 3.008 0 002.088 2.089c1.869.502 9.4.502 9.4.502s7.508 0 9.399-.502a3.008 3.008 0 002.089-2.09 31.258 31.26 0 00.5-5.784 31.258 31.26 0 00-.5-5.808zm-13.891 9.4V8.407l6.266 3.604z"></path>
</svg>
)
);
}
export function Mastodon(svgProps: SVGProps<SVGSVGElement>) {
@@ -57,5 +57,5 @@ export function Mastodon(svgProps: SVGProps<SVGSVGElement>) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z" />
</svg>
)
);
}

View File

@@ -1,4 +1,12 @@
import { Mail, Github, Facebook, Youtube, Linkedin, Twitter, Mastodon } from './icons'
import {
Mail,
Github,
Facebook,
Youtube,
Linkedin,
Twitter,
Mastodon,
} from './icons';
const components = {
mail: Mail,
@@ -8,33 +16,36 @@ const components = {
linkedin: Linkedin,
twitter: Twitter,
mastodon: Mastodon,
}
};
type SocialIconProps = {
kind: keyof typeof components
href: string | undefined
size?: number
}
kind: keyof typeof components;
href: string | undefined;
size?: number;
};
const SocialIcon = ({ kind, href, size = 8 }: SocialIconProps) => {
if (!href || (kind === 'mail' && !/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href)))
return null
if (
!href ||
(kind === 'mail' &&
!/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href))
)
return null;
const SocialSvg = components[kind]
const SocialSvg = components[kind];
return (
<a
className="text-sm text-gray-500 transition hover:text-gray-600"
target="_blank"
rel="noopener noreferrer"
href={href}
>
href={href}>
<span className="sr-only">{kind}</span>
<SocialSvg
className={`fill-current text-gray-700 hover:text-primary-500 dark:text-gray-200 dark:hover:text-primary-400 h-${size} w-${size}`}
/>
</a>
)
}
);
};
export default SocialIcon
export default SocialIcon;