⚒️ Preparate boilerplate v2 - Typescript + React 18.

This commit is contained in:
pheralb 2022-06-03 11:53:50 +01:00
parent 48e648a04b
commit b21f464108
46 changed files with 1374 additions and 12586 deletions

17
.gitignore vendored
View File

@ -23,20 +23,13 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# PWA files
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/sw.js.map
**/public/workbox-*.js.map
**/public/worker-*.js.map
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo

View File

@ -1,82 +0,0 @@
import { motion } from "framer-motion";
import React from "react";
const LoadingDot = {
display: "block",
width: "1rem",
height: "1rem",
backgroundColor: "#6748E6",
borderRadius: "50%",
};
const LoadingContainer = {
width: "5rem",
height: "5rem",
display: "flex",
justifyContent: "space-around",
};
const ContainerVariants = {
initial: {
transition: {
staggerChildren: 0.2,
},
},
animate: {
transition: {
staggerChildren: 0.2,
},
},
};
const DotVariants = {
initial: {
y: "0%",
},
animate: {
y: "100%",
},
};
const DotTransition = {
duration: 0.5,
yoyo: Infinity,
ease: "easeInOut",
};
export default function Loader() {
return (
<div
style={{
paddingTop: "5rem",
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<motion.div
style={LoadingContainer}
variants={ContainerVariants}
initial="initial"
animate="animate"
>
<motion.span
style={LoadingDot}
variants={DotVariants}
transition={DotTransition}
/>
<motion.span
style={LoadingDot}
variants={DotVariants}
transition={DotTransition}
/>
<motion.span
style={LoadingDot}
variants={DotVariants}
transition={DotTransition}
/>
</motion.div>
</div>
);
}

View File

@ -1,19 +0,0 @@
import React, { FC } from "react";
import { motion } from "framer-motion";
const Show = ({ children, delay }) => {
return (
<motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{
duration: 0.4,
delay: delay,
}}
>
{children}
</motion.div>
);
};
export default Show;

View File

@ -1,12 +0,0 @@
import React from "react";
import { motion } from "framer-motion";
const Tap = ({ children }) => {
return (
<motion.div whileHover={{ scale: 1.040 }} whileTap={{ scale: 0.98 }}>
{children}
</motion.div>
);
};
export default Tap;

View File

@ -1,23 +0,0 @@
import React from "react";
import { motion } from "framer-motion";
const Transitions = ({ children }) => {
return (
<motion.div
initial="initial"
animate="animate"
variants={{
initial: {
opacity: 0,
},
animate: {
opacity: 1,
},
}}
>
{children}
</motion.div>
);
};
export default Transitions;

View File

@ -1,26 +0,0 @@
import { Box, VStack, Icon, Text, Divider } from "@chakra-ui/react";
import React from "react";
const Design = ({ icon, title, children }) => {
return (
<Box
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
_hover={{ shadow: "md", transition: "all .2s" }}
>
<Box p="6">
<VStack spacing={3}>
<Icon as={icon} w={16} h={16} />
<Text fontSize="3xl" fontWeight="semibold">
{title}
</Text>
<Divider />
<Box fontSize="2xl">{children}</Box>
</VStack>
</Box>
</Box>
);
};
export default Design;

View File

@ -1,23 +0,0 @@
import { Box, Image } from "@chakra-ui/react";
import React from "react";
const CardImage = ({title, image, children}) => {
return (
<Box maxW="sm" borderWidth="1px" borderRadius="lg" overflow="hidden">
<Image src={image} alt={title} width="60" />
<Box p="6">
<Box
mt="1"
as="h4"
lineHeight="tight"
isTruncated
>
{title}
</Box>
{children}
</Box>
</Box>
);
};
export default CardImage;

View File

@ -1,87 +0,0 @@
import React from "react";
import Link from "next/link";
import {
Box,
Text,
Image,
Center,
HStack,
IconButton,
useColorModeValue,
} from "@chakra-ui/react";
import { IoCloudDownloadOutline } from "react-icons/io5";
import { FiExternalLink } from "react-icons/fi";
import download from "downloadjs";
import toast from "react-hot-toast";
import Tap from "animations/tap";
const Index = ({ title, url, href }) => {
const toastBg = useColorModeValue("#F2F2F2", "#1D1D1D");
const toastColor = useColorModeValue("black", "white");
const bgImage = useColorModeValue("transparent", "#E9E9E9");
const borderRds = useColorModeValue("0", "15px");
const downloadSvg = (url) => {
toast(`Downloading ${title}...`, {
icon: "🥳",
style: {
borderRadius: "10px",
background: toastBg,
color: toastColor,
},
});
download(url);
};
return (
<Box
p={4}
borderRadius="10px"
borderWidth="1px"
mb="2"
_hover={{
shadow: "md",
}}
transition="all 0.2s"
>
<Center>
<Image
src={href}
alt={title}
boxSize="45px"
bg={bgImage}
borderRadius={borderRds}
p="1"
/>
</Center>
<Text mt="2" fontWeight="light" textAlign="center">
{title}
</Text>
<Center>
<HStack spacing="1" mt="1">
<Tap>
<IconButton
as="button"
variant="ghost"
aria-label="Download SVG"
icon={<IoCloudDownloadOutline size="16" />}
onClick={() => downloadSvg(href)}
/>
</Tap>
<Tap>
<Link href={url} passHref>
<IconButton
as="a"
variant="ghost"
aria-label="Go to Vue SVG page"
icon={<FiExternalLink size="16" />}
/>
</Link>
</Tap>
</HStack>
</Center>
</Box>
);
};
export default Index;

View File

@ -1,38 +0,0 @@
import React from "react";
import { Box, Flex, Button, Container, Text, Icon } from "@chakra-ui/react";
import { IoHome, IoShapesOutline } from "react-icons/io5";
import Link from "next/link";
import Show from "animations/show";
const Error = () => {
return (
<>
<Show delay="0">
<Box px={{ base: 4, lg: 20 }} py={{ base: "3", md: "24" }}>
<Flex align="center" justify="center" direction="column" w="full">
<Icon name="error" boxSize="80px" mb="3" as={IoShapesOutline} />
<Text fontSize="40px" mb="2">
Oh no!
</Text>
<Text fontSize="20px" mb="3">
This page does not exist.
</Text>
<Link href="/" passHref>
<Button
leftIcon={<IoHome />}
borderWidth="1px"
variant="outline"
fontWeight="light"
mb="4"
>
Go home
</Button>
</Link>
</Flex>
</Box>
</Show>
</>
);
};
export default Error;

View File

@ -1,12 +0,0 @@
import React from 'react'
import { SimpleGrid } from '@chakra-ui/react'
const Index = ({children}) => {
return (
<SimpleGrid minChildWidth='200px' columns={3} spacing={5}>
{children}
</SimpleGrid>
)
}
export default Index

View File

@ -1,32 +0,0 @@
import React from "react";
import useSWR from "swr";
import Grid from "components/grid";
import Card from "components/card";
import Loader from "animations/loader";
const fetcher = (url) => fetch(url).then((res) => res.json());
const All = () => {
const { data, error } = useSWR("/api/all", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <Loader />;
return (
<>
<Grid>
{data.map((link) => (
<>
<div key={link}>
<Card
title={link.title}
url={`/svg/${link.id}`}
href={link.href}
/>
</div>
</>
))}
</Grid>
</>
);
};
export default All;

View File

@ -1,32 +0,0 @@
import React from "react";
import useSWR from "swr";
import Grid from "components/grid";
import Library from "components/card/library";
import Loader from "animations/loader";
const fetcher = (url) => fetch(url).then((res) => res.json());
const Libraries = () => {
const { data, error } = useSWR("/api/icons", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <Loader />;
return (
<>
<Grid>
{data.map((link) => (
<>
<div key={link}>
<Library
image={link.image}
title={link.title}
url={link.url}
/>
</div>
</>
))}
</Grid>
</>
);
};
export default Libraries;

View File

@ -1,12 +0,0 @@
import React from "react";
import { Box } from "@chakra-ui/react";
const Index = ({ children }) => {
return (
<Box as="main" px={{ base: 6, md: 16 }} pl={{ base: 6, md: 16 }}>
{children}
</Box>
);
};
export default Index;

View File

@ -1,133 +0,0 @@
import { useMemo, useRef, useState } from "react";
import { createAutocomplete } from "@algolia/autocomplete-core";
import {
Box,
Input,
InputLeftElement,
InputGroup,
Flex,
HStack,
Text,
Image,
Icon,
Link,
} from "@chakra-ui/react";
import { IoSearch } from "react-icons/io5";
import { FiExternalLink } from "react-icons/fi";
import { Algolia } from "components/svg";
import NextLink from "next/link";
const AutocompleteItem = ({ id, title, href, url }) => {
return (
<>
<NextLink href={`/svg/${id}`} passHref>
<Link
href={`/svg/${id}`}
style={{ textDecoration: "none" }}
_focus={{ outline: "0" }}
>
<Box
id={id}
w="100%"
borderWidth="1px"
borderRadius="6px"
mt="3"
cursor="pointer"
_hover={{ shadow: "md" }}
transition="all 0.2s"
>
<HStack py={6} px={6} spacing={2}>
<Image src={href} alt={title} boxSize="20px" mr="2" />
<Text fontSize="18px" fontWeight="light">
{title}
</Text>
<Icon as={FiExternalLink} />
</HStack>
</Box>
</Link>
</NextLink>
</>
);
};
export default function Search(props) {
const [autocompleteState, setAutocompleteState] = useState({
collections: [],
});
const autocomplete = useMemo(
() =>
createAutocomplete({
placeholder: "Search svgs...",
onStateChange: ({ state }) => setAutocompleteState(state),
getSources: () => [
{
sourceId: "svgs-next-api",
getItems: ({ query }) => {
if (!!query) {
return fetch(`/api/search?q=${query}`).then((res) =>
res.json()
);
}
},
},
],
...props,
}),
[props]
);
const formRef = useRef(null);
const inputRef = useRef(null);
const formProps = autocomplete.getFormProps({
inputElement: inputRef.current,
});
const inputProps = autocomplete.getInputProps({
inputElement: inputRef.current,
});
return (
<form ref={formRef} {...formProps}>
<Flex>
<InputGroup w="full">
<InputLeftElement pointerEvents="none" mt="1">
<IoSearch size="20" />
</InputLeftElement>
<Input
w="100%"
shadow="none"
size="lg"
type="tel"
placeholder="Search icons..."
_focus={{ shadow: "md" }}
ref={inputRef}
autoFocus
{...inputProps}
/>
</InputGroup>
<Box mt="4" ml="3" mr="2" cursor="pointer">
<Link href="https://www.algolia.com/" passHref>
<Algolia width="70px" />
</Link>
</Box>
</Flex>
<>
{autocompleteState.collections.map((collection, index) => {
const { items } = collection;
return (
<div key={`${index}`}>
{items.length > 0 && (
<ul {...autocomplete.getListProps()}>
{items.map((item) => (
<AutocompleteItem key={item.id} {...item} />
))}
</ul>
)}
</div>
);
})}
</>
</form>
);
}

View File

@ -1,41 +0,0 @@
import React from "react";
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useDisclosure,
IconButton,
Button,
useColorModeValue,
} from "@chakra-ui/react";
import Search from "components/search";
import { IoSearchOutline } from "react-icons/io5";
import Item from "components/sidebar/item";
const ModalSearch = (props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const bg = useColorModeValue("light.100", "dark.800");
return (
<>
<Item icon={IoSearchOutline} onClick={onOpen}>
Search
</Item>
<Modal isOpen={isOpen} onClose={onClose} motionPreset="slideInBottom">
<ModalOverlay />
<ModalContent bg={bg}>
<ModalHeader fontWeight="light">Search</ModalHeader>
<ModalCloseButton />
<ModalBody pb="5">
<Search />
</ModalBody>
</ModalContent>
</Modal>
</>
);
};
export default ModalSearch;

View File

@ -1,25 +0,0 @@
import React from "react";
import {
Icon,
Link,
Center,
useColorModeValue,
HStack,
} from "@chakra-ui/react";
import { IoRocketOutline } from "react-icons/io5";
const Index = () => {
const color = useColorModeValue("gray.400", "gray.600");
return (
<>
<HStack color={color} ml="6" spacing="3">
<Icon boxSize="6" as={IoRocketOutline} />
<Link href="https://github.com/pheralb" isExternal="true">
Built by Pablo
</Link>
</HStack>
</>
);
};
export default Index;

View File

@ -1,22 +0,0 @@
import React from "react";
import { useColorMode, useColorModeValue } from "@chakra-ui/react";
import { IoMoonOutline, IoSunnyOutline } from "react-icons/io5";
import Item from "./item";
const Index = () => {
const { colorMode, toggleColorMode } = useColorMode();
const iconChange = useColorModeValue(IoSunnyOutline, IoMoonOutline);
const theme = useColorModeValue("Light", "Dark");
function toggleTheme() {
toggleColorMode();
}
return (
<Item icon={iconChange} onClick={toggleTheme}>
{theme}
</Item>
);
};
export default Index;

View File

@ -1,115 +0,0 @@
import React from "react";
import {
Box,
Drawer,
DrawerContent,
DrawerOverlay,
DrawerCloseButton,
DrawerHeader,
DrawerBody,
Flex,
IconButton,
useColorModeValue,
useDisclosure,
Link,
} from "@chakra-ui/react";
import NextLink from "next/link";
import { IoApps } from "react-icons/io5";
import Logo from "components/sidebar/logo";
import Item from "components/sidebar/item";
import SidebarLinks from "components/sidebar/links";
import Dark from "components/sidebar/dark";
import By from "components/sidebar/by";
import ModalSearch from "components/search/modal";
export default function Index({ children }) {
const sidebar = useDisclosure();
const border = useColorModeValue("gray.200", "dark.800");
const bg = useColorModeValue("gray.100", "lightDark.900");
const SidebarContent = (props) => (
<Box
as="nav"
pos="fixed"
top="0"
left="0"
zIndex="sticky"
h="full"
pb="10"
overflowX="hidden"
overflowY="auto"
borderColor={border}
borderRightWidth="1px"
shadow="sm"
w="56"
{...props}
>
<Box px="5" pt="8" pb="5" align="center">
<Logo />
</Box>
<Flex direction="column" as="nav" aria-label="Main Navigation">
{SidebarLinks.map((link) => (
<NextLink key={link.id} href={link.href} passHref>
<Link
href={link.href}
isExternal={link.external}
style={{ textDecoration: "none" }}
>
<Item icon={link.icon} href={link.href} external={link.external}>
{link.title}
</Item>
</Link>
</NextLink>
))}
<ModalSearch />
<Dark />
</Flex>
<Box mt="8" align="center">
<By />
</Box>
</Box>
);
return (
<Box as="section" minH="100vh">
<SidebarContent display={{ base: "none", md: "unset" }} />
<Drawer
isOpen={sidebar.isOpen}
onClose={sidebar.onClose}
placement="left"
>
<DrawerOverlay />
<DrawerContent bg={bg}>
<DrawerCloseButton borderWidth="1px" />
<DrawerBody>
<SidebarContent pt="6" borderRight="none" />
</DrawerBody>
</DrawerContent>
</Drawer>
<Box ml={{ base: 0, md: 56 }} transition=".3s ease">
<Box
as="header"
align="center"
justify="space-between"
w="full"
p="5"
display={{ base: "inline-flex", md: "none" }}
>
<IconButton
aria-label="Menu"
onClick={sidebar.onOpen}
icon={<IoApps />}
size="md"
w="100%"
variant="ghost"
borderWidth="1px"
/>
</Box>
<Box as="main" pt={{ base: "0", md: "6" }} pb={{ base: "0", md: "6" }}>
{children}
</Box>
</Box>
</Box>
);
}

View File

@ -1,35 +0,0 @@
import React from "react";
import { useRouter } from 'next/router';
import { Flex, Icon, useColorModeValue } from "@chakra-ui/react";
import { FiExternalLink } from "react-icons/fi";
import Tap from "animations/tap";
const Item = (props) => {
const { icon, external, children, href, ...rest } = props;
const { pathname } = useRouter();
const isActive = pathname === href;
const borderColor = useColorModeValue("dark.800", "white");
return (
<Tap>
<Flex
align="center"
px="5"
pl="4"
py="4"
cursor="pointer"
transition=".15s ease"
borderColor={borderColor}
borderLeftWidth={isActive ? "2px" : ''}
{...rest}
>
{icon && <Icon ml="2" mr="4" boxSize="6" as={icon} />}
{children}
{external && <Icon ml="3" mr="4" boxSize="4" as={FiExternalLink} />}
</Flex>
</Tap>
);
};
export default Item;

View File

@ -1,28 +0,0 @@
import { IoAppsOutline, IoLogoGithub, IoBookOutline } from "react-icons/io5";
import { FiTwitter } from "react-icons/fi";
const SidebarLinks = [
{
id: 1,
href: "/",
external: false,
title: "Browse",
icon: IoAppsOutline,
},
{
id: 2,
href: "https://github.com/pheralb/svgl/",
external: true,
title: "Github",
icon: IoLogoGithub,
},
{
id: 3,
href: "https://twitter.com/pheralb_",
external: true,
title: "Twitter",
icon: FiTwitter,
},
];
export default SidebarLinks;

View File

@ -1,30 +0,0 @@
import React from "react";
import Link from "next/link";
import { HStack, Icon, Text } from "@chakra-ui/react";
import { svgl } from "components/svg";
import Tap from "animations/tap";
const Logo = () => {
return (
<Tap>
<Link href="/" passHref>
<HStack cursor="pointer">
<Icon
as={svgl}
name="logo"
boxSize="30px"
mr="2"
ml="1"
borderRadius="full"
bg="transparent"
/>
<Text fontSize="2xl" ml="2">
svgl
</Text>
</HStack>
</Link>
</Tap>
);
};
export default Logo;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +0,0 @@
{
"compilerOptions": {
"baseUrl": "./"
}
}

5
next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,15 +1,6 @@
const withPWA = require("next-pwa");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
}
module.exports = withPWA({
pwa: {
dest: "public",
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === "development",
},
nextConfig,
});
module.exports = nextConfig

10896
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,7 @@
{
"name": "svgl",
"version": "1.2.0",
"description": "Beautiful SVG vector logos",
"author": "pheralb",
"license": "MIT",
"name": "svgl-new",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
@ -11,24 +9,16 @@
"lint": "next lint"
},
"dependencies": {
"@algolia/autocomplete-core": "^1.5.6",
"@chakra-ui/react": "^1.8.7",
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"canvas-confetti": "^1.5.1",
"downloadjs": "^1.4.7",
"framer-motion": "^6.2.8",
"next": "12.1.0",
"next-pwa": "^5.5.0",
"nextjs-progressbar": "^0.0.14",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hot-toast": "^2.2.0",
"react-icons": "^4.3.1",
"swr": "^1.2.2"
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0"
},
"devDependencies": {
"eslint": "8.10.0",
"eslint-config-next": "12.1.0"
"@types/node": "17.0.38",
"@types/react": "18.0.10",
"@types/react-dom": "18.0.5",
"eslint": "8.16.0",
"eslint-config-next": "12.1.6",
"typescript": "4.7.2"
}
}

View File

@ -1,8 +0,0 @@
import Error from 'components/error';
import React from 'react';
const Error404 = () => {
return <Error />;
};
export default Error404;

View File

@ -1,78 +0,0 @@
// 🖤 Next Head ->
import Head from "next/head";
// 🌿 Chakra UI ->
import { ChakraProvider, Container, useColorModeValue } from "@chakra-ui/react";
// ➡️ Nextjs Progressbar ->
import NextNProgress from "nextjs-progressbar";
// 📦 Components ->
import Sidebar from "components/sidebar";
import Layout from "components/layout";
import Footer from "components/sidebar/by";
// 💙 Global CSS ->
import "styles/globals.css";
// 🎨 Theme ->
import theme from "styles/theme";
// 🐢 Animations ->
import Transitions from "animations/transitions";
import { Toaster } from "react-hot-toast";
function MyApp({ Component, pageProps, router }) {
const progress = useColorModeValue("#7B7B7B", "#D4D4D4");
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SVGL - Beautiful SVG vector logos</title>
<meta property="og:title" content="SVGL - Beautiful SVG vector logos" />
<meta
property="og:description"
content="Beautiful SVG logos. Free and open source."
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://svgl.vercel.app/" />
<meta
property="og:image"
content="https://svgl.vercel.app/images/banner.png"
/>
<meta name="twitter:site" content="@pheralb_" />
<meta
property="twitter:title"
content="SVGL - Beautiful SVG vector logos"
/>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:creator" content="@pheralb" />
<meta
property="twitter:description"
content="Beautiful SVG logos. Free and open source."
/>
<meta
name="twitter:image"
content="https://svgl.vercel.app/images/banner.png"
/>
<meta name="keywords" content="svg,vector,logo,logos,download" />
<meta content="#16161a" name="theme-color" />
<link rel="icon" href="/icons/icon.ico" />
</Head>
<ChakraProvider theme={theme}>
<Sidebar>
<NextNProgress color={progress}/>
<Layout>
<Transitions key={router.route}>
<Component {...pageProps} />
</Transitions>
</Layout>
</Sidebar>
</ChakraProvider>
<Toaster position="bottom-center" reverseOrder={false} />
</>
);
}
export default MyApp;

8
pages/_app.tsx Normal file
View File

@ -0,0 +1,8 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp

View File

@ -1,22 +0,0 @@
import { ColorModeScript } from "@chakra-ui/react";
import NextDocument, { Html, Head, Main, NextScript } from "next/document";
import theme from "styles/theme";
export default class Document extends NextDocument {
render() {
return (
<Html>
<Head>
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/icons/icon-512x512.png"></link>
<meta name="theme-color" content="#36558F" />
</Head>
<body>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@ -1,38 +0,0 @@
import React from "react";
import { Box, Flex, Button, Text, Icon } from "@chakra-ui/react";
import { IoHome, IoWarning } from "react-icons/io5";
import Link from "next/link";
import Show from "animations/show";
const Offline = () => {
return (
<>
<Show delay="0">
<Box px={{ base: 4, lg: 20 }} py={{ base: "3", md: "24" }}>
<Flex align="center" justify="center" direction="column" w="full">
<Icon name="error" boxSize="80px" mb="3" as={IoWarning} />
<Text fontSize="40px" mb="2">
Oh no!
</Text>
<Text fontSize="20px" mb="3">
No internet connection
</Text>
<Link href="/" passHref>
<Button
leftIcon={<IoHome />}
borderWidth="1px"
variant="outline"
fontWeight="light"
mb="4"
>
Refresh
</Button>
</Link>
</Flex>
</Box>
</Show>
</>
);
};
export default Offline;

View File

@ -1,6 +0,0 @@
import db from "data/svgs";
// 📦 Show all content ->
export default function handler(req, res) {
res.status(200).json(db);
}

View File

@ -1,13 +0,0 @@
import db from "data/svgs";
// 📦 Show categories ->
export default function handler(req, res) {
try {
const categories = db
.map((item) => item.category)
.filter((category, index, self) => self.indexOf(category) === index);
return res.status(200).json(categories);
} catch (err) {
res.status(400).json({ message: err });
}
}

13
pages/api/hello.ts Normal file
View File

@ -0,0 +1,13 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}

View File

@ -1,32 +0,0 @@
import db from "data/svgs";
export default function handler(req, res) {
const { id, q, c } = req.query;
// 🔎 Search by id (ex: ?id=1) ->
if (id) {
const item = db.find((item) => item.id === +id);
return res.status(200).json(item);
}
// 🔎 Search by query (ex: ?q=d) ->
if (q) {
const results = db.filter((product) => {
const { title } = product;
return title.toLowerCase().includes(q.toLowerCase());
});
return res.status(200).json(results);
}
// 🔎 Search by category (ex: ?c=library) ->
if (c) {
const results = db.filter((product) => {
const { category } = product;
return category.toLowerCase().includes(c.toLowerCase());
});
return res.status(200).json(results);
}
// ✖ Error ->
res.status(400).json({ info: 'Error: api query not found.' });
}

View File

@ -1,116 +0,0 @@
import React from "react";
import Head from "next/head";
import download from "downloadjs";
import { Box, Button, Flex, HStack, Link, Text } from "@chakra-ui/react";
import Show from "animations/show";
import Grid from "components/grid";
import DesignCard from "components/card/design";
import { RiFontSize, RiPaletteLine } from "react-icons/ri";
import { IoMdImages } from "react-icons/io";
import { HiOutlineExternalLink } from "react-icons/hi";
import { IoCloudDownloadOutline } from "react-icons/io5";
import CardImage from "components/card/image";
const Design = () => {
const downloadSvg = (url) => {
download(url);
};
return (
<>
<Head>
<title>Design - SVGL</title>
</Head>
<Box mt="6">
<Box w="full" border="solid 1px transparent">
<Show>
<Text
as="h1"
fontSize={{ base: "25px", sm: "35px", md: "5xl", lg: "6xl" }}
letterSpacing="tight"
lineHeight="short"
fontWeight="extrabold"
mb="3"
>
Design
</Text>
</Show>
<Show delay={0.3}>
<Box mt={{ base: 4, md: 5 }}>
<Grid>
<DesignCard title="Fonts" icon={RiFontSize}>
<HStack spacing={2}>
<Link
href="https://fonts.google.com/specimen/Poppins"
isExternal
>
Poppins
</Link>
<HiOutlineExternalLink size="20px" />
</HStack>
</DesignCard>
<DesignCard title="Colors" icon={RiPaletteLine}>
<Flex color="white">
<Box bg="lightDark.900" p="2">
<Text fontSize="15px">#16161a</Text>
</Box>
<Box bg="light.100" p="2">
<Text fontSize="15px" color="black">
#f9f9f9
</Text>
</Box>
<Box bg="#6748E6" p="2">
<Text fontSize="15px" color="white">
#6748E6
</Text>
</Box>
</Flex>
</DesignCard>
</Grid>
<Box mt="4">
<DesignCard title="Images" icon={IoMdImages}>
<HStack spacing={3}>
<CardImage title="Banner" image="/images/banner.png">
<Button
w={{ base: "100%", md: "auto" }}
leftIcon={<IoCloudDownloadOutline />}
variant="primary"
fontWeight="light"
mt="2"
onClick={() =>
downloadSvg(
"https://svgl.vercel.app/images/banner.png"
)
}
>
Download
</Button>
</CardImage>
<CardImage title="Logo" image="/images/logo.png">
<Button
w={{ base: "100%", md: "auto" }}
leftIcon={<IoCloudDownloadOutline />}
variant="primary"
fontWeight="light"
mt="2"
onClick={() =>
downloadSvg(
"https://svgl.vercel.app/images/logo.png"
)
}
>
Download
</Button>
</CardImage>
</HStack>
</DesignCard>
</Box>
</Box>
</Show>
</Box>
</Box>
</>
);
};
export default Design;

View File

@ -1,19 +0,0 @@
import { chakra, Box } from "@chakra-ui/react";
import Search from "components/search";
import Items from "components/items/all";
import Loader from "animations/loader";
export default function Index() {
return (
<>
<Box mt="6">
<Box w="full" border="solid 1px transparent">
<Search />
<Box mt={{ base: 4, md: 8 }}>
<Items />
</Box>
</Box>
</Box>
</>
);
}

72
pages/index.tsx Normal file
View File

@ -0,0 +1,72 @@
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
const Home: NextPage = () => {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.tsx</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/canary/examples"
className={styles.card}
>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h2>Deploy &rarr;</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
export default Home

View File

@ -1,134 +0,0 @@
import Head from "next/head";
import {
chakra,
Box,
Flex,
SimpleGrid,
Button,
Image,
Center,
Link,
useColorModeValue,
} from "@chakra-ui/react";
import { useRouter } from "next/router";
import useSWR from "swr";
import Error from "components/error";
import { IoArrowBackOutline, IoCloudDownloadOutline } from "react-icons/io5";
import { BiLinkExternal } from "react-icons/bi";
import Show from "animations/show";
import Loader from "animations/loader";
import confetti from "canvas-confetti";
import download from "downloadjs";
import NextLink from 'next/link';
const fetcher = async (url) => {
const res = await fetch(url);
const data = await res.json();
if (res.status !== 200) {
throw new Error(data.message);
}
return data;
};
export default function Icon() {
const { query } = useRouter();
const { data, error } = useSWR(
() => query.id && `/api/search?id=${query.id}`,
fetcher
);
const bgImage = useColorModeValue("transparent", "#E9E9E9");
const borderRds = useColorModeValue("0", "15px");
if (error) return <Error />;
if (!data) return <Loader />;
const downloadSvg = (url) => {
confetti({
particleCount: 200,
startVelocity: 30,
spread: 300,
gravity: 1.2,
origin: { y: 0 },
});
download(url);
};
return (
<>
<Head>
<title>{data.title} - SVGL</title>
</Head>
<Show delay="0">
<NextLink href="/" passHref>
<Button
leftIcon={<IoArrowBackOutline />}
fontWeight="light"
variant="ghost"
mb="4"
>
Continue discovering
</Button>
</NextLink>
<SimpleGrid columns={{ base: 1, md: 1, lg: 2 }} spacing={0}>
<Box py={{ base: "10", md: "24" }}>
<Center>
<Image
src={data.href}
alt={data.title}
w={{ base: "30%", md: "20%", lg: "30%" }}
fit="cover"
loading="lazy"
bg={bgImage}
borderRadius={borderRds}
p="1"
/>
</Center>
</Box>
<Flex
direction="column"
alignItems="start"
justifyContent="center"
px={{ base: 4, lg: 4 }}
py={{ base: "3", md: "0", lg: "10" }}
>
<chakra.h1
mb={3}
fontSize={{ base: "4xl", md: "4xl", lg: "5xl" }}
fontWeight="semibold"
lineHeight="shorter"
>
{data.title}
</chakra.h1>
<Flex direction={{ base: "column", md: "row" }} w="100%" mt="2">
<Button
w={{ base: "100%", md: "auto" }}
mb={{ base: "2", md: "0" }}
leftIcon={<IoCloudDownloadOutline />}
variant="primary"
fontWeight="light"
mr="2"
onClick={() => downloadSvg(data.href)}
>
Download .svg
</Button>
<Link
href={data.url}
style={{ textDecoration: "none" }}
isExternal
>
<Button
w={{ base: "100%", md: "auto" }}
fontWeight="light"
borderWidth="1px"
rightIcon={<BiLinkExternal />}
>
{data.title} website
</Button>
</Link>
</Flex>
</Flex>
</SimpleGrid>
</Show>
</>
);
}

116
styles/Home.module.css Normal file
View File

@ -0,0 +1,116 @@
.container {
padding: 0 2rem;
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
display: flex;
flex: 1;
padding: 2rem 0;
border-top: 1px solid #eaeaea;
justify-content: center;
align-items: center;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
margin: 4rem 0;
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
}
.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
max-width: 300px;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
margin-left: 0.5rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}

View File

@ -1,86 +0,0 @@
const baseStyle = {
borderRadius: "md",
borderColor: "dark.400",
boxShadow: "none",
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
_disabled: {
cursor: "not-allowed",
},
_focused: {
borderColor: "dark.500",
},
};
function variantPrimary() {
const disabled = {
bg: "dark.300",
color: "dark.500",
};
const loading = {
bg: "dark.600",
color: "white",
};
return {
bg: "dark.700",
color: "white",
_hover: {
bg: "dark.800",
_disabled: {
...disabled,
_loading: loading,
},
},
_active: {
bg: "dark.800",
},
_disabled: {
...disabled,
_loading: loading,
},
};
}
function variantSecondary() {
const disabled = {
bg: "dark.300",
color: "gray.500",
};
const loading = {
bg: "dark.600",
color: "white",
};
return {
bg: "dark.500",
color: "white",
_hover: {
bg: "dark.700",
_disabled: {
...disabled,
_loading: loading,
},
},
_active: {
bg: "dark.800",
},
_disabled: {
...disabled,
_loading: loading,
},
};
}
const variants = {
primary: variantPrimary,
secondary: variantSecondary,
};
export default {
baseStyle,
variants,
};

View File

@ -1,5 +0,0 @@
import Button from "./button";
export default {
Button,
};

View File

@ -1,11 +1,16 @@
@font-face {
font-family: "Inter-Medium";
src: url("/fonts/Inter-Medium.woff2") format('woff2');
font-style: normal;
font-weight: 400;
font-display: swap;
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a:focus {
outline: none;
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

View File

@ -1,51 +0,0 @@
import { extendTheme } from "@chakra-ui/react";
import { mode } from "@chakra-ui/theme-tools";
import components from "./components";
const theme = extendTheme(
{
components,
},
{
config: {
initialColorMode: "light",
useSystemColorMode: false,
},
colors: {
dark: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e6e6e6",
300: "#d6d6d6",
400: "#a5a5a5",
500: "#767676",
600: "#575757",
700: "#434343",
800: "#292929",
900: "#000000",
},
lightDark: {
900: "#16161a",
},
light: {
100: "#f9f9f9",
},
},
fonts: {
body: "Inter-Medium, sans-serif",
heading: "Inter-Medium, sans-serif",
},
styles: {
global: (props) => ({
"html, body": {
height: "100%",
maxHeight: "100vh",
backgroundColor: mode("light.100", "lightDark.900")(props),
},
}),
borderColor: "red",
},
}
);
export default theme;

20
tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}