Feature/site detail page (#99)
* site: pull data from "icons" dir * site: display icons * site: remove redundant code * site: colour mode support * site: header * site: order imports * site: search * site: add toast when copying icon * site: styling * site: hero * fix: disable theme toggle transitions * feat: Use Yarn Workspaces * refactor: Update site deploy scripts * refactor: Remove dark mode for now * feat: Add site title * refactor: Fix warning and format * feat: Add dark mode back 👀 * feat: Escape key to reset query * Fix by aelfric * Add Github link * Fix #40 * Add site overlay * sort categories * Add detail page * Add first categories * add box * move site to root directory * fix merge issues * Fix routing issues * Fix icon overlay * Add copy and download icon * Fix style issues * Add text * update chakra UI * remove import * update dependecies * add lucide react * Fix bugs * delete stats files * update charkra version Co-authored-by: John Letey <johnletey@gmail.com> Co-authored-by: appmachine <appmachine@appmachines-iMac.local>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { CSSReset, ThemeProvider, ColorModeProvider } from '@chakra-ui/core';
|
||||
import { CSSReset, ChakraProvider, ColorModeProvider } from '@chakra-ui/core';
|
||||
import customTheme from '../lib/theme';
|
||||
import '../assets/styling.css';
|
||||
import Head from 'next/head';
|
||||
|
||||
const App = ({ Component, pageProps }) => {
|
||||
@@ -8,12 +9,9 @@ const App = ({ Component, pageProps }) => {
|
||||
<Head>
|
||||
<title>Lucide</title>
|
||||
</Head>
|
||||
<ThemeProvider theme={customTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<Component {...pageProps} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
<ChakraProvider theme={customTheme}>
|
||||
<Component {...pageProps} />
|
||||
</ChakraProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import { ColorModeScript } from "@chakra-ui/core"
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
@@ -6,7 +7,7 @@ class MyDocument extends Document {
|
||||
<Html>
|
||||
<Head>
|
||||
<link
|
||||
href="https://indestructibletype.com/fonts/Jost.css"
|
||||
href="https://fonts.googleapis.com/css2?family=Mukta:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
@@ -20,6 +21,7 @@ class MyDocument extends Document {
|
||||
}
|
||||
`}</style>
|
||||
<body>
|
||||
<ColorModeScript />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
39
site/src/pages/icon/[iconName].tsx
Normal file
39
site/src/pages/icon/[iconName].tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import IconDetailOverlay from '../../components/IconDetailOverlay'
|
||||
import { getAllData, getData } from '../../lib/icons';
|
||||
import IconOverview from '../../components/IconOverview';
|
||||
import Layout from '../../components/Layout';
|
||||
import Header from '../../components/Header';
|
||||
|
||||
const IconPage = ({ icon, data }) => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<IconDetailOverlay
|
||||
icon={icon}
|
||||
onClose={() => router.push('/')}
|
||||
/>
|
||||
<Header {...{data}}/>
|
||||
<IconOverview {...{data}}/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconPage
|
||||
|
||||
export function getStaticProps({ params: { iconName } }) {
|
||||
const data = getAllData();
|
||||
const icon = getData(iconName);
|
||||
return { props: { icon, data } }
|
||||
}
|
||||
|
||||
export function getStaticPaths() {
|
||||
return {
|
||||
paths: getAllData().map(({name: iconName }) => ({
|
||||
params: { iconName },
|
||||
})),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
@@ -1,148 +1,31 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Grid,
|
||||
Link,
|
||||
Icon,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Stack,
|
||||
Text,
|
||||
useToast,
|
||||
} from '@chakra-ui/core';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import download from 'downloadjs';
|
||||
import JSZip from 'jszip';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Layout from '../components/Layout';
|
||||
import { getAllData } from '../lib/icons';
|
||||
import useSearch from '../lib/search';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useDebounce } from '../lib/useDebounce';
|
||||
import Layout from "../components/Layout";
|
||||
import { getAllData } from "../lib/icons";
|
||||
|
||||
function generateZip(icons) {
|
||||
const zip = new JSZip();
|
||||
Object.values(icons).forEach((icon) =>
|
||||
// @ts-ignore
|
||||
zip.file(`${icon.name}.svg`, icon.src)
|
||||
);
|
||||
return zip.generateAsync({ type: 'blob' });
|
||||
}
|
||||
import IconOverview from "../components/IconOverview";
|
||||
import IconDetailOverlay from "../components/IconDetailOverlay";
|
||||
import { useRouter } from "next/router";
|
||||
import Header from "../components/Header";
|
||||
|
||||
const IndexPage = ({ data }) => {
|
||||
const router = useRouter();
|
||||
const { query } = router.query;
|
||||
const [queryText, setQueryText] = useState(query || '');
|
||||
const toast = useToast();
|
||||
const debouncedQuery = useDebounce(queryText, 1000);
|
||||
const results = useSearch(data, queryText);
|
||||
|
||||
useEffect(() => {
|
||||
setQueryText(query);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
router.push({
|
||||
pathname: '/',
|
||||
query: { query: debouncedQuery },
|
||||
});
|
||||
}, [debouncedQuery]);
|
||||
|
||||
const inputElement = useRef(null);
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === '/' && inputElement.current !== document.activeElement) {
|
||||
event.preventDefault();
|
||||
inputElement.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
const getIcon = (iconName) => data.find(({name}) => name === iconName) || {};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text fontSize="3xl" as="b" mb="4">
|
||||
Simply beautiful open source icons, community-sourced
|
||||
</Text>
|
||||
<Text fontSize="lg" as="p" textAlign="center" mb="8">
|
||||
An open-source icon library, a fork of Feather Icons. <br/>We're expanding the icon set as much as possible while keeping it nice-looking - <Link href="https://github.com/lucide-icons/lucide" isExternal>join us</Link>!
|
||||
</Text>
|
||||
<Stack isInline marginTop={3} marginBottom={10}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const zip = await generateZip(data);
|
||||
download(zip, 'feather.zip');
|
||||
}}
|
||||
>
|
||||
Download all
|
||||
</Button>
|
||||
</Stack>
|
||||
</Flex>
|
||||
<InputGroup position="sticky" top={2} zIndex={1}>
|
||||
<InputLeftElement children={<Icon name="search" />} />
|
||||
<Input
|
||||
ref={inputElement}
|
||||
placeholder={`Search ${Object.keys(data).length} icons (Press "/" to focus)`}
|
||||
value={queryText}
|
||||
onChange={(event) => setQueryText(event.target.value)}
|
||||
marginBottom={5}
|
||||
/>
|
||||
</InputGroup>
|
||||
{results.length > 0 ? (
|
||||
<Grid templateColumns={`repeat(auto-fill, minmax(160px, 1fr))`} gap={5}>
|
||||
{results.map((icon) => {
|
||||
// @ts-ignore
|
||||
const actualIcon = icon.item ? icon.item : icon;
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
rounded="lg"
|
||||
padding={16}
|
||||
onClick={(event) => {
|
||||
if (event.shiftKey) {
|
||||
copy(actualIcon.src);
|
||||
toast({
|
||||
title: 'Copied!',
|
||||
description: `Icon "${actualIcon.name}" copied to clipboard.`,
|
||||
status: 'success',
|
||||
duration: 1500,
|
||||
});
|
||||
} else {
|
||||
download(actualIcon.src, `${actualIcon.name}.svg`, 'image/svg+xml');
|
||||
}
|
||||
}}
|
||||
key={actualIcon.name}
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<div dangerouslySetInnerHTML={{ __html: actualIcon.src }} />
|
||||
<Text marginTop={5}>{actualIcon.name}</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
) : (
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
textAlign="center"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
No results found for "{query}"
|
||||
</Text>
|
||||
)}
|
||||
<IconDetailOverlay
|
||||
isOpen={!!router.query.iconName}
|
||||
icon={getIcon(router.query.iconName)}
|
||||
onClose={() => router.push('/')}
|
||||
/>
|
||||
<Header {...{data}}/>
|
||||
<IconOverview {...{data}}/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
let data = getAllData();
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
|
||||
Reference in New Issue
Block a user