feat: Site (#1)
* 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 Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
This commit is contained in:
@@ -1,237 +0,0 @@
|
||||
{
|
||||
"activity": ["pulse", "health", "action", "motion"],
|
||||
"airplay": ["stream", "cast", "mirroring"],
|
||||
"alert-circle": ["warning", "alert", "danger"],
|
||||
"alert-octagon": ["warning", "alert", "danger"],
|
||||
"alert-triangle": ["warning", "alert", "danger"],
|
||||
"align-center": ["text alignment", "center"],
|
||||
"align-justify": ["text alignment", "justified"],
|
||||
"align-left": ["text alignment", "left"],
|
||||
"align-right": ["text alignment", "right"],
|
||||
"anchor": [],
|
||||
"archive": ["index", "box"],
|
||||
"at-sign": ["mention", "at", "email", "message"],
|
||||
"award": ["achievement", "badge"],
|
||||
"aperture": ["camera", "photo"],
|
||||
"bar-chart": ["statistics", "diagram", "graph"],
|
||||
"bar-chart-2": ["statistics", "diagram", "graph"],
|
||||
"battery": ["power", "electricity"],
|
||||
"battery-charging": ["power", "electricity"],
|
||||
"bell": ["alarm", "notification", "sound"],
|
||||
"bell-off": ["alarm", "notification", "silent"],
|
||||
"bluetooth": ["wireless"],
|
||||
"book-open": ["read", "library"],
|
||||
"book": ["read", "dictionary", "booklet", "magazine", "library"],
|
||||
"bookmark": ["read", "clip", "marker", "tag"],
|
||||
"box": ["cube"],
|
||||
"briefcase": ["work", "bag", "baggage", "folder"],
|
||||
"calendar": ["date"],
|
||||
"camera": ["photo"],
|
||||
"cast": ["chromecast", "airplay"],
|
||||
"circle": ["off", "zero", "record"],
|
||||
"clipboard": ["copy"],
|
||||
"clock": ["time", "watch", "alarm"],
|
||||
"cloud-drizzle": ["weather", "shower"],
|
||||
"cloud-lightning": ["weather", "bolt"],
|
||||
"cloud-rain": ["weather"],
|
||||
"cloud-snow": ["weather", "blizzard"],
|
||||
"cloud": ["weather"],
|
||||
"codepen": ["logo"],
|
||||
"codesandbox": ["logo"],
|
||||
"code": ["source", "programming"],
|
||||
"coffee": ["drink", "cup", "mug", "tea", "cafe", "hot", "beverage"],
|
||||
"columns": ["layout"],
|
||||
"command": ["keyboard", "cmd", "terminal", "prompt"],
|
||||
"compass": ["navigation", "safari", "travel", "direction"],
|
||||
"copy": ["clone", "duplicate"],
|
||||
"corner-down-left": ["arrow", "return"],
|
||||
"corner-down-right": ["arrow"],
|
||||
"corner-left-down": ["arrow"],
|
||||
"corner-left-up": ["arrow"],
|
||||
"corner-right-down": ["arrow"],
|
||||
"corner-right-up": ["arrow"],
|
||||
"corner-up-left": ["arrow"],
|
||||
"corner-up-right": ["arrow"],
|
||||
"cpu": ["processor", "technology"],
|
||||
"credit-card": ["purchase", "payment", "cc"],
|
||||
"crop": ["photo", "image"],
|
||||
"crosshair": ["aim", "target"],
|
||||
"database": ["storage", "memory"],
|
||||
"delete": ["remove"],
|
||||
"disc": ["album", "cd", "dvd", "music"],
|
||||
"dollar-sign": ["currency", "money", "payment"],
|
||||
"droplet": ["water"],
|
||||
"edit": ["pencil", "change"],
|
||||
"edit-2": ["pencil", "change"],
|
||||
"edit-3": ["pencil", "change"],
|
||||
"eye": ["view", "watch"],
|
||||
"eye-off": ["view", "watch", "hide", "hidden"],
|
||||
"external-link": ["outbound"],
|
||||
"facebook": ["logo", "social"],
|
||||
"fast-forward": ["music"],
|
||||
"figma": ["logo", "design", "tool"],
|
||||
"file-minus": ["delete", "remove", "erase"],
|
||||
"file-plus": ["add", "create", "new"],
|
||||
"file-text": ["data", "txt", "pdf"],
|
||||
"film": ["movie", "video"],
|
||||
"filter": ["funnel", "hopper"],
|
||||
"flag": ["report"],
|
||||
"folder-minus": ["directory"],
|
||||
"folder-plus": ["directory"],
|
||||
"folder": ["directory"],
|
||||
"framer": ["logo", "design", "tool"],
|
||||
"frown": ["emoji", "face", "bad", "sad", "emotion"],
|
||||
"gift": ["present", "box", "birthday", "party"],
|
||||
"git-branch": ["code", "version control"],
|
||||
"git-commit": ["code", "version control"],
|
||||
"git-merge": ["code", "version control"],
|
||||
"git-pull-request": ["code", "version control"],
|
||||
"github": ["logo", "version control"],
|
||||
"gitlab": ["logo", "version control"],
|
||||
"globe": ["world", "browser", "language", "translate"],
|
||||
"hard-drive": ["computer", "server", "memory", "data"],
|
||||
"hash": ["hashtag", "number", "pound"],
|
||||
"headphones": ["music", "audio", "sound"],
|
||||
"heart": ["like", "love", "emotion"],
|
||||
"help-circle": ["question mark"],
|
||||
"hexagon": ["shape", "node.js", "logo"],
|
||||
"home": ["house", "living"],
|
||||
"image": ["picture"],
|
||||
"inbox": ["email"],
|
||||
"instagram": ["logo", "camera"],
|
||||
"key": ["password", "login", "authentication", "secure"],
|
||||
"layers": ["stack"],
|
||||
"layout": ["window", "webpage"],
|
||||
"life-bouy": ["help", "life ring", "support"],
|
||||
"link": ["chain", "url"],
|
||||
"link-2": ["chain", "url"],
|
||||
"linkedin": ["logo", "social media"],
|
||||
"list": ["options"],
|
||||
"lock": ["security", "password", "secure"],
|
||||
"log-in": ["sign in", "arrow", "enter"],
|
||||
"log-out": ["sign out", "arrow", "exit"],
|
||||
"mail": ["email", "message"],
|
||||
"map-pin": ["location", "navigation", "travel", "marker"],
|
||||
"map": ["location", "navigation", "travel"],
|
||||
"maximize": ["fullscreen"],
|
||||
"maximize-2": ["fullscreen", "arrows", "expand"],
|
||||
"meh": ["emoji", "face", "neutral", "emotion"],
|
||||
"menu": ["bars", "navigation", "hamburger"],
|
||||
"message-circle": ["comment", "chat"],
|
||||
"message-square": ["comment", "chat"],
|
||||
"mic-off": ["record", "sound", "mute"],
|
||||
"mic": ["record", "sound", "listen"],
|
||||
"minimize": ["exit fullscreen", "close"],
|
||||
"minimize-2": ["exit fullscreen", "arrows", "close"],
|
||||
"minus": ["subtract"],
|
||||
"monitor": ["tv", "screen", "display"],
|
||||
"moon": ["dark", "night"],
|
||||
"more-horizontal": ["ellipsis"],
|
||||
"more-vertical": ["ellipsis"],
|
||||
"mouse-pointer": ["arrow", "cursor"],
|
||||
"move": ["arrows"],
|
||||
"music": ["note"],
|
||||
"navigation": ["location", "travel"],
|
||||
"navigation-2": ["location", "travel"],
|
||||
"octagon": ["stop"],
|
||||
"package": ["box", "container"],
|
||||
"paperclip": ["attachment"],
|
||||
"pause": ["music", "stop"],
|
||||
"pause-circle": ["music", "audio", "stop"],
|
||||
"pen-tool": ["vector", "drawing"],
|
||||
"percent": ["discount"],
|
||||
"phone-call": ["ring"],
|
||||
"phone-forwarded": ["call"],
|
||||
"phone-incoming": ["call"],
|
||||
"phone-missed": ["call"],
|
||||
"phone-off": ["call", "mute"],
|
||||
"phone-outgoing": ["call"],
|
||||
"phone": ["call"],
|
||||
"play": ["music", "start"],
|
||||
"pie-chart": ["statistics", "diagram"],
|
||||
"play-circle": ["music", "start"],
|
||||
"plus": ["add", "new"],
|
||||
"plus-circle": ["add", "new"],
|
||||
"plus-square": ["add", "new"],
|
||||
"pocket": ["logo", "save"],
|
||||
"power": ["on", "off"],
|
||||
"printer": ["fax", "office", "device"],
|
||||
"radio": ["signal"],
|
||||
"refresh-cw": ["synchronise", "arrows"],
|
||||
"refresh-ccw": ["arrows"],
|
||||
"repeat": ["loop", "arrows"],
|
||||
"rewind": ["music"],
|
||||
"rotate-ccw": ["arrow"],
|
||||
"rotate-cw": ["arrow"],
|
||||
"rss": ["feed", "subscribe"],
|
||||
"save": ["floppy disk"],
|
||||
"scissors": ["cut"],
|
||||
"search": ["find", "magnifier", "magnifying glass"],
|
||||
"send": ["message", "mail", "email", "paper airplane", "paper aeroplane"],
|
||||
"settings": ["cog", "edit", "gear", "preferences"],
|
||||
"share-2": ["network", "connections"],
|
||||
"shield": ["security", "secure"],
|
||||
"shield-off": ["security", "insecure"],
|
||||
"shopping-bag": ["ecommerce", "cart", "purchase", "store"],
|
||||
"shopping-cart": ["ecommerce", "cart", "purchase", "store"],
|
||||
"shuffle": ["music"],
|
||||
"skip-back": ["music"],
|
||||
"skip-forward": ["music"],
|
||||
"slack": ["logo"],
|
||||
"slash": ["ban", "no"],
|
||||
"sliders": ["settings", "controls"],
|
||||
"smartphone": ["cellphone", "device"],
|
||||
"smile": ["emoji", "face", "happy", "good", "emotion"],
|
||||
"speaker": ["audio", "music"],
|
||||
"star": ["bookmark", "favorite", "like"],
|
||||
"stop-circle": ["media", "music"],
|
||||
"sun": ["brightness", "weather", "light"],
|
||||
"sunrise": ["weather", "time", "morning", "day"],
|
||||
"sunset": ["weather", "time", "evening", "night"],
|
||||
"tablet": ["device"],
|
||||
"tag": ["label"],
|
||||
"target": ["logo", "bullseye"],
|
||||
"terminal": ["code", "command line", "prompt"],
|
||||
"thermometer": ["temperature", "celsius", "fahrenheit", "weather"],
|
||||
"thumbs-down": ["dislike", "bad", "emotion"],
|
||||
"thumbs-up": ["like", "good", "emotion"],
|
||||
"toggle-left": ["on", "off", "switch"],
|
||||
"toggle-right": ["on", "off", "switch"],
|
||||
"tool": ["settings", "spanner"],
|
||||
"trash": ["garbage", "delete", "remove", "bin"],
|
||||
"trash-2": ["garbage", "delete", "remove", "bin"],
|
||||
"triangle": ["delta"],
|
||||
"truck": ["delivery", "van", "shipping", "transport", "lorry"],
|
||||
"tv": ["television", "stream"],
|
||||
"twitch": ["logo"],
|
||||
"twitter": ["logo", "social"],
|
||||
"type": ["text"],
|
||||
"umbrella": ["rain", "weather"],
|
||||
"unlock": ["security"],
|
||||
"user-check": ["followed", "subscribed"],
|
||||
"user-minus": ["delete", "remove", "unfollow", "unsubscribe"],
|
||||
"user-plus": ["new", "add", "create", "follow", "subscribe"],
|
||||
"user-x": ["delete", "remove", "unfollow", "unsubscribe", "unavailable"],
|
||||
"user": ["person", "account"],
|
||||
"users": ["group"],
|
||||
"video-off": ["camera", "movie", "film"],
|
||||
"video": ["camera", "movie", "film"],
|
||||
"voicemail": ["phone"],
|
||||
"volume": ["music", "sound", "mute"],
|
||||
"volume-1": ["music", "sound"],
|
||||
"volume-2": ["music", "sound"],
|
||||
"volume-x": ["music", "sound", "mute"],
|
||||
"watch": ["clock", "time"],
|
||||
"wifi-off": ["disabled"],
|
||||
"wifi": ["connection", "signal", "wireless"],
|
||||
"wind": ["weather", "air"],
|
||||
"x-circle": ["cancel", "close", "delete", "remove", "times", "clear"],
|
||||
"x-octagon": ["delete", "stop", "alert", "warning", "times", "clear"],
|
||||
"x-square": ["cancel", "close", "delete", "remove", "times", "clear"],
|
||||
"x": ["cancel", "close", "delete", "remove", "times", "clear"],
|
||||
"youtube": ["logo", "video", "play"],
|
||||
"zap-off": ["flash", "camera", "lightning"],
|
||||
"zap": ["flash", "camera", "lightning"],
|
||||
"zoom-in": ["magnifying glass"],
|
||||
"zoom-out": ["magnifying glass"]
|
||||
}
|
||||
1
packages/site/.prettierignore
Normal file
1
packages/site/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
.next
|
||||
2
packages/site/next-env.d.ts
vendored
Normal file
2
packages/site/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
32
packages/site/package.json
Normal file
32
packages/site/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "site",
|
||||
"version": "1.0.0",
|
||||
"author": "John Letey",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"export": "next export",
|
||||
"deploy": "yarn build && yarn export"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/core": "^0.8.0",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"downloadjs": "^1.4.7",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"fuse.js": "^6.0.4",
|
||||
"jszip": "^3.4.0",
|
||||
"next": "^9.4.4",
|
||||
"query-string": "^6.13.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"use-query-params": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.11",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"typescript": "^3.9.5"
|
||||
}
|
||||
}
|
||||
58
packages/site/src/components/Layout.tsx
Normal file
58
packages/site/src/components/Layout.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Box, Divider, Flex, Text, Link, Icon, useColorMode } from "@chakra-ui/core";
|
||||
import { StringParam, useQueryParam } from "use-query-params";
|
||||
import { useKeyBindings } from "../lib/key";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const [, setQuery] = useQueryParam("query", StringParam);
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
|
||||
useKeyBindings({
|
||||
Escape: {
|
||||
fn: () => setQuery(""),
|
||||
},
|
||||
KeyT: {
|
||||
fn: () => toggleColorMode(),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Box h="100vh">
|
||||
<Flex mb={16} w="full">
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
pt={4}
|
||||
pb={4}
|
||||
maxW="1250px"
|
||||
margin="0 auto"
|
||||
w="full"
|
||||
px={8}
|
||||
>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="4xl"
|
||||
onClick={() => setQuery("")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
Featherity
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Link href="https://github.com/featherity/featherity" isExternal style={{ fontSize: "18px", marginRight: '24px' }}>
|
||||
Github
|
||||
</Link>
|
||||
<div onClick={toggleColorMode} style={{ cursor: "pointer" }}>
|
||||
<Icon name={colorMode == "light" ? "moon" : "sun"} size="24px" />
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex margin="0 auto" direction="column" maxW="1250px" px={8}>
|
||||
{children}
|
||||
<Divider marginTop={10} marginBottom={10} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
32
packages/site/src/lib/icons.tsx
Normal file
32
packages/site/src/lib/icons.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import tags from '../../../../tags.json';
|
||||
|
||||
const directory = path.join(process.cwd(), "../../icons");
|
||||
|
||||
export function getAllNames() {
|
||||
const fileNames = fs.readdirSync(directory);
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
return fileName.replace(/\.svg$/, "");
|
||||
});
|
||||
}
|
||||
|
||||
export function getData(name) {
|
||||
const fullPath = path.join(directory, `${name}.svg`);
|
||||
const fileContents = fs.readFileSync(fullPath, "utf8");
|
||||
|
||||
return {
|
||||
name,
|
||||
tags: tags[name] || [],
|
||||
src: fileContents,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAllData() {
|
||||
const names = getAllNames();
|
||||
|
||||
return names.map((name) => {
|
||||
return getData(name);
|
||||
});
|
||||
}
|
||||
34
packages/site/src/lib/key.js
Normal file
34
packages/site/src/lib/key.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const isCtrl = (e) => e.metaKey || e.ctrlKey;
|
||||
|
||||
// https://keycode.info
|
||||
export const useKeyBindings = (
|
||||
initialKeyBindings = {},
|
||||
eventListener = "keydown"
|
||||
) => {
|
||||
const [keyBindings] = useState(initialKeyBindings);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener(
|
||||
eventListener,
|
||||
(event) => {
|
||||
const { code } = event;
|
||||
const keyBinding = keyBindings[code];
|
||||
if (keyBinding === undefined) return;
|
||||
const condition = keyBinding.ctrl ? isCtrl(event) : true;
|
||||
if (!condition) return;
|
||||
if (event.target.type != "text" || code == "Escape") {
|
||||
event.preventDefault();
|
||||
keyBinding.fn(event);
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
return () =>
|
||||
Object.keys(keyBindings).forEach((keyBinding) =>
|
||||
document.removeEventListener(eventListener, keyBindings[keyBinding])
|
||||
);
|
||||
}, []);
|
||||
};
|
||||
23
packages/site/src/lib/search.tsx
Normal file
23
packages/site/src/lib/search.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Fuse from "fuse.js";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function useSearch(icons, query) {
|
||||
const fuse = new Fuse(Object.values(icons), {
|
||||
threshold: 0.2,
|
||||
keys: ["name", "tags"],
|
||||
});
|
||||
|
||||
const [results, setResults] = useState(Object.values(icons));
|
||||
|
||||
useEffect(() => {
|
||||
if (query.trim()) {
|
||||
setResults(fuse.search(query.trim()));
|
||||
} else {
|
||||
setResults(Object.values(icons));
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export default useSearch;
|
||||
74
packages/site/src/lib/theme.tsx
Normal file
74
packages/site/src/lib/theme.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { theme as chakraTheme } from "@chakra-ui/core";
|
||||
|
||||
const theme = {
|
||||
...chakraTheme,
|
||||
fonts: {
|
||||
...chakraTheme.fonts,
|
||||
body: `Jost,-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"`,
|
||||
},
|
||||
icons: {
|
||||
...chakraTheme.icons,
|
||||
sun: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
moon: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
search: {
|
||||
path: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default theme;
|
||||
47
packages/site/src/pages/_app.tsx
Normal file
47
packages/site/src/pages/_app.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { CSSReset, ThemeProvider, ColorModeProvider } from "@chakra-ui/core";
|
||||
import { useRouter } from "next/router";
|
||||
import { QueryParamProvider } from "use-query-params";
|
||||
import customTheme from "../lib/theme";
|
||||
import Head from "next/head";
|
||||
|
||||
const QueryProvider = ({ children }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const history = {
|
||||
push: ({ search }: Location) =>
|
||||
router.push({ search, pathname: router.pathname }),
|
||||
|
||||
replace: ({ search }: Location) =>
|
||||
router.replace({ search, pathname: router.pathname }),
|
||||
};
|
||||
|
||||
const location = {
|
||||
search: router.asPath.replace(/[^?]+/u, ""),
|
||||
} as Location;
|
||||
|
||||
return (
|
||||
<QueryParamProvider history={history} location={location}>
|
||||
{children}
|
||||
</QueryParamProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const App = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Featherity</title>
|
||||
</Head>
|
||||
<QueryProvider>
|
||||
<ThemeProvider theme={customTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<Component {...pageProps} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
</QueryProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
31
packages/site/src/pages/_document.tsx
Normal file
31
packages/site/src/pages/_document.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<link
|
||||
href="https://indestructibletype.com/fonts/Jost.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
* {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
`}</style>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
143
packages/site/src/pages/index.tsx
Normal file
143
packages/site/src/pages/index.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Grid,
|
||||
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 { StringParam, useQueryParam } from "use-query-params";
|
||||
import Layout from "../components/Layout";
|
||||
import { getAllData } from "../lib/icons";
|
||||
import useSearch from "../lib/search";
|
||||
|
||||
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" });
|
||||
}
|
||||
|
||||
const IndexPage = ({ data }) => {
|
||||
const [query, setQuery] = useQueryParam("query", StringParam);
|
||||
const results = useSearch(data, query || "");
|
||||
const toast = useToast();
|
||||
|
||||
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);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text fontSize="3xl" as="b">
|
||||
Simply beautiful open source icons, community-sourced
|
||||
</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={query}
|
||||
onChange={(event) => setQuery(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>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
let data = getAllData();
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default IndexPage;
|
||||
19
packages/site/tsconfig.json
Normal file
19
packages/site/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
Reference in New Issue
Block a user