+
+
+ {name}
+ ([...categories, ...name.split("-"), ...tags])
+ )}
+ isDark={isDark}
+ />
+
+
React
- {snippets.react}
+ {snippets.react}
@@ -123,73 +180,80 @@ const InfoPanel: React.FC
= (props) => {
Vue
- {snippets.vue}
+ {snippets.vue}
HTML/CSS
-
- {snippets.html}
+
+ {snippets.html}
-
-
- setOpen(false)}
- onKeyDown={(e) => {
- e.key === "Enter" && setOpen(false);
- }}
- />
+
+
+ setOpen(false)}
+ onKeyDown={(e) => {
+ e.key === "Enter" && setOpen(false);
+ }}
+ />
+
);
};
-export default InfoPanel;
+export default DetailsPanel;
diff --git a/src/components/IconGrid/IconGrid.css b/src/components/IconGrid/IconGrid.css
index c431e22..179b074 100644
--- a/src/components/IconGrid/IconGrid.css
+++ b/src/components/IconGrid/IconGrid.css
@@ -1,5 +1,5 @@
.grid-container {
- padding: 0px 16px 4px;
+ padding: 32px 16px;
min-height: 80vh;
}
@@ -22,12 +22,14 @@
margin: 4px;
border-radius: 16px;
user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
cursor: pointer;
+ /* transition: background-color 100ms ease; */
}
.grid-item:hover {
background-color: rgba(163, 159, 171, 0.1);
- transition: background-color 0.2s;
}
.grid-item:focus {
@@ -48,11 +50,9 @@
display: flex;
width: 100%;
height: 0px;
- margin: 0px;
+ margin: 0 4px;
border-radius: 16px;
background-color: rgba(163, 159, 171, 0.1);
- overflow-y: hidden;
- overflow-x: auto;
}
@media screen and (max-width: 1023px) {
@@ -66,17 +66,16 @@
}
.icon-preview {
- height: 396px;
width: 30%;
display: flex;
text-align: center;
flex-direction: column;
align-items: center;
- justify-content: center;
+ margin-top: 72px;
}
.icon-preview p {
- margin: 12px 0 0;
+ margin: 0;
font-size: 12px;
line-height: 16px;
}
@@ -91,8 +90,8 @@
}
.snippet pre {
- /* white-space: nowrap; */
- /* overflow: hidden; */
+ display: flex;
+ align-items: center;
text-overflow: ellipsis;
color: black;
user-select: all;
@@ -112,12 +111,15 @@
}
}
+.snippet span {
+ flex: 1;
+}
+
.snippet button {
background-color: transparent;
margin: 0;
padding: 0;
height: 24px;
- float: right;
cursor: pointer;
}
@@ -127,6 +129,7 @@
.button-row {
display: flex;
+ flex-wrap: wrap;
}
.button-row button {
@@ -156,7 +159,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
- padding: 0 16px 4px;
+ padding: 32px 16px;
min-height: 80vh;
max-width: 1120px;
margin: auto;
@@ -167,9 +170,10 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ margin-bottom: 0;
}
.beacon {
position: relative;
- top: -64px;
+ top: -96px;
}
diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx
index 2d7b88c..803329c 100644
--- a/src/components/IconGrid/IconGrid.tsx
+++ b/src/components/IconGrid/IconGrid.tsx
@@ -10,9 +10,12 @@ import {
} from "../../state/selectors";
import useGridSpans from "../../hooks/useGridSpans";
import IconGridItem from "./IconGridItem";
-import Warn from "../Warn/Warn";
+import TagCloud from "./TagCloud";
+import Notice from "../Notice/Notice";
import "./IconGrid.css";
+const defaultSearchTags = ["communication", "editor", "emoji", "maps", "weather"];
+
type IconGridProps = {};
const IconGrid: React.FC
= () => {
@@ -31,7 +34,13 @@ const IconGrid: React.FC = () => {
controls.start("visible");
}, [controls, filteredQueryResults]);
- if (!filteredQueryResults.length) return ;
+ if (!filteredQueryResults.length)
+ return (
+
+ Try searching a category or keyword:
+
+
+ );
return (
@@ -52,7 +61,7 @@ const IconGrid: React.FC = () => {
index={index}
spans={spans}
isDark={isDark}
- {...iconEntry}
+ entry={iconEntry}
originOffset={originOffset}
/>
))}
diff --git a/src/components/IconGrid/IconGridItem.tsx b/src/components/IconGrid/IconGridItem.tsx
index 3c9e100..ecd2ee4 100644
--- a/src/components/IconGrid/IconGridItem.tsx
+++ b/src/components/IconGrid/IconGridItem.tsx
@@ -6,17 +6,16 @@ import React, {
} from "react";
import { useRecoilState } from "recoil";
import { motion, AnimatePresence } from "framer-motion";
-import { IconProps, Icon } from "phosphor-react";
import { iconPreviewOpenAtom } from "../../state/atoms";
-import InfoPanel from "./InfoPanel";
+import DetailsPanel from "./DetailsPanel";
+import { IconEntry } from "../../lib";
-interface IconGridItemProps extends IconProps {
+interface IconGridItemProps {
index: number;
spans: number;
isDark: boolean;
- name: string;
- Icon: Icon;
+ entry: IconEntry;
originOffset: MutableRefObject<{ top: number; left: number }>;
}
@@ -33,7 +32,8 @@ const itemVariants = {
};
const IconGridItem: React.FC = (props) => {
- const { index, originOffset, name, Icon } = props;
+ const { index, originOffset, entry } = props;
+ const { name, Icon } = entry;
const [open, setOpen] = useRecoilState(iconPreviewOpenAtom);
const isOpen = open === name;
const delayRef = useRef(0);
@@ -87,7 +87,7 @@ const IconGridItem: React.FC = (props) => {
{name}
- {isOpen && }
+ {isOpen && }
>
);
diff --git a/src/components/IconGrid/TagCloud.css b/src/components/IconGrid/TagCloud.css
new file mode 100644
index 0000000..1ee8f94
--- /dev/null
+++ b/src/components/IconGrid/TagCloud.css
@@ -0,0 +1,32 @@
+.tag-cloud {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ padding: 24px;
+}
+
+button.tag-button {
+ margin: 4px;
+ border-radius: 4px;
+ background-color: rgba(194, 186, 196, 0.25);
+ outline: none;
+ cursor: pointer;
+ transition: background-color 200ms ease, box-shadow 200ms ease;
+}
+
+button.tag-button:hover {
+ background-color: rgba(194, 186, 196, 0.7);
+}
+
+button.tag-button:focus {
+ box-shadow: 0 0 0 1px rgba(194, 186, 196, 0.7);
+}
+
+.tag-button code {
+ padding: 4px;
+ font-size: 12px;
+}
+
+.dark {
+ color: white;
+}
\ No newline at end of file
diff --git a/src/components/IconGrid/TagCloud.tsx b/src/components/IconGrid/TagCloud.tsx
new file mode 100644
index 0000000..5d376b4
--- /dev/null
+++ b/src/components/IconGrid/TagCloud.tsx
@@ -0,0 +1,38 @@
+import React, { useCallback } from "react";
+import { useSetRecoilState } from "recoil";
+
+import { searchQueryAtom } from "../../state/atoms";
+import "./TagCloud.css";
+
+interface TagCloudProps {
+ name: string;
+ tags: string[];
+ isDark: boolean;
+}
+
+const TagCloud: React.FC = ({ name, tags, isDark }) => {
+ const setQuery = useSetRecoilState(searchQueryAtom);
+ const handleTagClick = useCallback(
+ (tag: string) => {
+ setQuery(tag);
+ document.getElementById("search-input")?.focus();
+ },
+ [setQuery]
+ );
+
+ return (
+
+ {tags.map((tag) => (
+ void handleTagClick(tag)}
+ >
+ {tag}
+
+ ))}
+
+ );
+};
+
+export default TagCloud;
diff --git a/src/components/Links/Links.css b/src/components/Links/Links.css
new file mode 100644
index 0000000..fd241ef
--- /dev/null
+++ b/src/components/Links/Links.css
@@ -0,0 +1,41 @@
+.links {
+ display: flex;
+ flex-flow: column wrap;
+ align-content: flex-start;
+ align-items: flex-start;
+ justify-content: center;
+ /* column-gap: 72px; */
+ /* -webkit-column-gap: 72px; */
+ margin: 32px 0 64px;
+ max-height: 144px;
+}
+
+.links > div {
+ margin: 0 72px 24px 0;
+ display: flex;
+ align-items: center;
+}
+
+.links svg {
+ margin-right: 12px;
+}
+
+a.nav-link {
+ text-decoration: none;
+ position: relative;
+ color: black;
+}
+
+a.nav-link:after {
+ content: "";
+ position: absolute;
+ bottom: -2px;
+ left: 0;
+ width: 0%;
+ border-bottom: 1px solid black;
+ transition: 0.2s;
+}
+
+a.nav-link:hover:after {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/components/Links/Links.tsx b/src/components/Links/Links.tsx
new file mode 100644
index 0000000..378a1b4
--- /dev/null
+++ b/src/components/Links/Links.tsx
@@ -0,0 +1,80 @@
+import React from "react";
+import { OutboundLink } from "react-ga";
+import { ArrowElbowDownRight } from "phosphor-react";
+
+import "./Links.css";
+
+interface LinksProps {}
+
+const Links: React.FC = () => {
+ return (
+
+ );
+};
+
+export default Links;
diff --git a/src/components/Warn/Warn.tsx b/src/components/Notice/Notice.tsx
similarity index 55%
rename from src/components/Warn/Warn.tsx
rename to src/components/Notice/Notice.tsx
index 8b213de..a89d20f 100644
--- a/src/components/Warn/Warn.tsx
+++ b/src/components/Notice/Notice.tsx
@@ -4,13 +4,14 @@ import { useRecoilValue } from "recoil";
import { isDarkThemeSelector } from "../../state/selectors";
import { searchQueryAtom } from "../../state/atoms";
-import { SmileyXEyes } from "phosphor-react";
+import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react";
-interface WarnProps {
+interface NoticeProps {
message?: string;
+ type?: "wait" | "help" | "warn" | "none";
}
-const Warn: React.FC = ({ message }) => {
+const Notice: React.FC = ({ message, type = "warn", children }) => {
const isDark = useRecoilValue(isDarkThemeSelector);
const query = useRecoilValue(searchQueryAtom);
@@ -22,15 +23,24 @@ const Warn: React.FC = ({ message }) => {
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
-
+ {type === "wait" && (
+
+ )}
+ {type === "help" && (
+
+ )}
+ {type === "warn" && (
+
+ )}
{message ?? (
No results for "{query}"
)}
+ {children}
);
};
-export default Warn;
+export default Notice;
diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx
index 739fde0..f407d25 100644
--- a/src/components/SearchInput/SearchInput.tsx
+++ b/src/components/SearchInput/SearchInput.tsx
@@ -1,7 +1,8 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import { useRecoilState } from "recoil";
import { useDebounce } from "react-use";
import { MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
+import ReactGA from "react-ga";
import { searchQueryAtom } from "../../state/atoms";
import "./SearchInput.css";
@@ -11,15 +12,27 @@ type SearchInputProps = {};
const SearchInput: React.FC