mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
⚒️ Project structure.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { LayoutProps } from "@/interfaces/components";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
const Grid = (props: LayoutProps) => {
|
||||
return (
|
||||
<SimpleGrid minChildWidth="200px" spacing="30px" >
|
||||
{props.children}
|
||||
</SimpleGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Grid;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { CustomIconBtnProps } from "@/interfaces/components";
|
||||
import { IconButton } from "@chakra-ui/react";
|
||||
|
||||
const CustomIconBtn = (props: CustomIconBtnProps) => {
|
||||
return (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label={props.title}
|
||||
icon={props.icon}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomIconBtn;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { Link } from "@chakra-ui/react";
|
||||
import NextLink from "next/link";
|
||||
import { CustomLinkProps } from "@/interfaces/components";
|
||||
|
||||
const CustomLink = ({ href, children, external }: CustomLinkProps) => {
|
||||
return (
|
||||
<NextLink href={href} passHref>
|
||||
<Link isExternal={external} _hover={{ textDecoration: "none" }}>
|
||||
{children}
|
||||
</Link>
|
||||
</NextLink>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomLink;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { SVGCardProps } from "@/interfaces/components";
|
||||
import { Box, Flex, Image, useColorModeValue } from "@chakra-ui/react";
|
||||
|
||||
const SVGCard = (props: SVGCardProps) => {
|
||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
||||
return (
|
||||
<Box bg={bg} shadow="sm" maxW="50%" borderWidth="2px" borderRadius="10px" p="5">
|
||||
<Flex direction="column" justifyContent="center" alignItems="center">
|
||||
<Image boxSize="50px" src={props.svg} alt={props.title} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SVGCard;
|
||||
@@ -0,0 +1,21 @@
|
||||
export interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface CustomLinkProps {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
export interface CustomIconBtnProps {
|
||||
title: string;
|
||||
icon: React.ReactElement;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface SVGCardProps {
|
||||
title: string;
|
||||
svg: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface SvgData {
|
||||
id: number;
|
||||
href: string;
|
||||
title: string;
|
||||
category: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
useColorModeValue,
|
||||
HStack,
|
||||
Button,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
IconButton,
|
||||
CloseButton,
|
||||
Heading,
|
||||
Container,
|
||||
} from "@chakra-ui/react";
|
||||
import { IoMenu } from "react-icons/io5";
|
||||
import Items from "@/layout/header/items";
|
||||
import VersionItem from "@/layout/header/version";
|
||||
|
||||
const Header = () => {
|
||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
||||
const mobileNav = useDisclosure();
|
||||
return (
|
||||
<Box as="header" position="sticky" top="0" bg={bg} w="full" py={6} mb={10}>
|
||||
<Container maxW="85%">
|
||||
<Flex alignItems="center" justifyContent="space-between" mx="auto">
|
||||
<HStack>
|
||||
<Heading fontSize="18px">svgl</Heading>
|
||||
</HStack>
|
||||
<HStack display="flex" alignItems="center" spacing={1}>
|
||||
<HStack
|
||||
spacing={2}
|
||||
mr={1}
|
||||
display={{ base: "none", md: "inline-flex" }}
|
||||
>
|
||||
<Items />
|
||||
</HStack>
|
||||
<Box display={{ base: "inline-flex", md: "none" }}>
|
||||
<IconButton
|
||||
display={{ base: "flex", md: "none" }}
|
||||
aria-label="Open menu header"
|
||||
variant="ghost"
|
||||
icon={<IoMenu size={32} />}
|
||||
onClick={mobileNav.onOpen}
|
||||
/>
|
||||
|
||||
<VStack
|
||||
pos="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
display={mobileNav.isOpen ? "flex" : "none"}
|
||||
flexDirection="column"
|
||||
p={2}
|
||||
pb={4}
|
||||
m={2}
|
||||
bg={bg}
|
||||
spacing={3}
|
||||
rounded="sm"
|
||||
shadow="sm"
|
||||
>
|
||||
<CloseButton
|
||||
aria-label="Close menu"
|
||||
onClick={mobileNav.onClose}
|
||||
/>
|
||||
|
||||
<Button w="full" variant="ghost">
|
||||
Features
|
||||
</Button>
|
||||
<Button w="full" variant="ghost">
|
||||
Pricing
|
||||
</Button>
|
||||
<Button w="full" variant="ghost">
|
||||
Blog
|
||||
</Button>
|
||||
<Button w="full" variant="ghost">
|
||||
Company
|
||||
</Button>
|
||||
<Button w="full" variant="ghost">
|
||||
Sign in
|
||||
</Button>
|
||||
</VStack>
|
||||
</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import VersionItem from "@/layout/header/version";
|
||||
import ThemeItem from "@/layout/header/theme";
|
||||
import Socials from "@/layout/header/socials";
|
||||
|
||||
const Items = () => {
|
||||
return (
|
||||
<>
|
||||
<VersionItem />
|
||||
<ThemeItem />
|
||||
<Socials />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Items;
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import CustomIconBtn from "@/common/iconBtn";
|
||||
import CustomLink from "@/common/link";
|
||||
import { IoLogoGithub, IoLogoTwitter } from "react-icons/io5";
|
||||
|
||||
const Socials = () => {
|
||||
return (
|
||||
<>
|
||||
<CustomLink href="https://github.com/pheralb/svgl" external={true}>
|
||||
<CustomIconBtn title="Github" icon={<IoLogoGithub size={20} />} />
|
||||
</CustomLink>
|
||||
<CustomLink href="https://twitter.com/pheralb_" external={true}>
|
||||
<CustomIconBtn title="Twitter" icon={<IoLogoTwitter size={20} />} />
|
||||
</CustomLink>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Socials;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useColorMode, useColorModeValue } from "@chakra-ui/react";
|
||||
import CustomIconBtn from "@/common/iconBtn";
|
||||
import { IoMoonOutline, IoSunnyOutline } from "react-icons/io5";
|
||||
|
||||
const Theme = () => {
|
||||
const { toggleColorMode } = useColorMode();
|
||||
const key = useColorModeValue("light", "dark");
|
||||
const icon = useColorModeValue(
|
||||
<IoMoonOutline size={20} />,
|
||||
<IoSunnyOutline size={20} />
|
||||
);
|
||||
return (
|
||||
<AnimatePresence exitBeforeEnter initial={false}>
|
||||
<motion.div
|
||||
style={{ display: "inline-block" }}
|
||||
key={key}
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 20, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<CustomIconBtn
|
||||
title="Toggle theme"
|
||||
icon={icon}
|
||||
onClick={toggleColorMode}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
export default Theme;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Menu,
|
||||
Button,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
useDisclosure,
|
||||
MenuItem,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from "@chakra-ui/react";
|
||||
import useSWR from "swr";
|
||||
import { IoCaretDownOutline } from "react-icons/io5";
|
||||
import { githubVersionPackage } from "@/services";
|
||||
import CustomLink from "@/common/link";
|
||||
|
||||
const VersionItem = () => {
|
||||
const { data, error } = useSWR(githubVersionPackage);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
||||
|
||||
if (error) return <Text>Error loading version</Text>;
|
||||
if (!data) return <Text>loading...</Text>;
|
||||
|
||||
return (
|
||||
<Menu isOpen={isOpen}>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
borderRadius={5}
|
||||
aria-label="Version dropdown"
|
||||
rightIcon={<IoCaretDownOutline size={14} />}
|
||||
_focus={{
|
||||
borderColor: "transparent",
|
||||
}}
|
||||
onMouseEnter={onOpen}
|
||||
onMouseLeave={onClose}
|
||||
>
|
||||
{data?.tag_name}
|
||||
</MenuButton>
|
||||
<MenuList onMouseEnter={onOpen} onMouseLeave={onClose} bg={bg}>
|
||||
<CustomLink href={data?.html_url} external={true}>
|
||||
<MenuItem>Releases</MenuItem>
|
||||
</CustomLink>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionItem;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { LayoutProps } from "@/interfaces/components";
|
||||
import React from "react";
|
||||
import Header from "@/layout/header";
|
||||
import { Box } from "@chakra-ui/react";
|
||||
|
||||
const Index = ({ children }: LayoutProps) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
// Chakra UI & custom styles ->
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
import theme from "@/theme";
|
||||
import "@/styles/globals.css";
|
||||
|
||||
// Layout ->
|
||||
import Layout from "@/layout";
|
||||
|
||||
// SWR Config & services ->
|
||||
import { SWRConfig } from "swr";
|
||||
import { fetcher } from "@/services/fetcher";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<ChakraProvider theme={theme}>
|
||||
<SWRConfig value={{ fetcher }}>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</SWRConfig>
|
||||
</ChakraProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ColorModeScript } from "@chakra-ui/react";
|
||||
import NextDocument, { Html, Head, Main, NextScript } from "next/document";
|
||||
import theme from "@/theme";
|
||||
|
||||
export default class Document extends NextDocument {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import db from "data/svgs";
|
||||
import { SvgData } from "@/interfaces/svgData";
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<SvgData[]>
|
||||
) {
|
||||
res.status(200).json(db);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { NextPage } from "next";
|
||||
import { Center, Container, Heading } from "@chakra-ui/react";
|
||||
import useSWR from "swr";
|
||||
import { getAllSvgs } from "@/services";
|
||||
import { SvgData } from "@/interfaces/svgData";
|
||||
import SVGCard from "@/components/svgCard";
|
||||
import Grid from "@/common/grid";
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const { data, error } = useSWR(getAllSvgs);
|
||||
|
||||
if (error) return <div>failed to load</div>;
|
||||
if (!data) return <div>loading...</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>
|
||||
<Heading fontSize="5xl">Beautifully SVG logos</Heading>
|
||||
</Center>
|
||||
<Container maxW="85%">
|
||||
<Grid>
|
||||
{data.map((svg: SvgData) => (
|
||||
<SVGCard
|
||||
key={svg.id}
|
||||
title={svg.title}
|
||||
url={svg.href}
|
||||
svg={svg.href}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,10 @@
|
||||
export const fetcher = async (url: string) => {
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = new Error("An error occurred while fetching the data.");
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export const githubVersionPackage = 'https://api.github.com/repos/pheralb/svgl/releases/latest';
|
||||
export const getAllSvgs = "/api/all";
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Fonts -> */
|
||||
@font-face {
|
||||
font-family: "Inter-Regular";
|
||||
src: url("/fonts/Inter-Regular.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter-Semibold";
|
||||
src: url("/fonts/Inter-Semibold.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const baseStyle = {
|
||||
borderRadius: "md",
|
||||
fontWeight: "light",
|
||||
};
|
||||
|
||||
export default {
|
||||
baseStyle,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import Button from "./button";
|
||||
|
||||
export default {
|
||||
Button,
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ChakraProps, 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: {
|
||||
bg: {
|
||||
light: "#f2f2f3",
|
||||
dark: "#242424",
|
||||
},
|
||||
full: {
|
||||
light: "#ffffff",
|
||||
dark: "#000000",
|
||||
}
|
||||
},
|
||||
fonts: {
|
||||
body: "Inter-Regular, sans-serif",
|
||||
heading: "Inter-Semibold, sans-serif",
|
||||
},
|
||||
styles: {
|
||||
global: (props: ChakraProps) => ({
|
||||
"html, body": {
|
||||
height: "100%",
|
||||
maxHeight: "100vh",
|
||||
background: mode(
|
||||
"radial-gradient(circle at 1px 1px, #E7E7E7 1px, #f2f2f3 0)",
|
||||
"radial-gradient(circle at 1px 1px, #303030 1px, #242424 0)"
|
||||
)(props),
|
||||
backgroundSize: "40px 40px",
|
||||
fontSize: "14px",
|
||||
},
|
||||
}),
|
||||
borderColor: "red",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default theme;
|
||||
Reference in New Issue
Block a user