diff --git a/package.json b/package.json
index 94b0e31..47989a2 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
+ "recoil": "^0.0.10",
"typescript": "~3.7.2"
},
"scripts": {
@@ -35,5 +36,6 @@
"last 1 firefox version",
"last 1 safari version"
]
- }
+ },
+ "devDependencies": {}
}
diff --git a/src/App.tsx b/src/App.tsx
deleted file mode 100644
index a53698a..0000000
--- a/src/App.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import logo from './logo.svg';
-import './App.css';
-
-function App() {
- return (
-
- );
-}
-
-export default App;
diff --git a/src/App.css b/src/components/App/App.css
similarity index 96%
rename from src/App.css
rename to src/components/App/App.css
index 74b5e05..a3c3134 100644
--- a/src/App.css
+++ b/src/components/App/App.css
@@ -15,7 +15,6 @@
.App-header {
background-color: #282c34;
- min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
@@ -26,6 +25,7 @@
.App-link {
color: #61dafb;
+ size: 1em;
}
@keyframes App-logo-spin {
diff --git a/src/App.test.tsx b/src/components/App/App.test.tsx
similarity index 100%
rename from src/App.test.tsx
rename to src/components/App/App.test.tsx
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
new file mode 100644
index 0000000..dbd6f6f
--- /dev/null
+++ b/src/components/App/App.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import { IconSearch, IconGrid } from "../";
+
+const App = () => {
+ return (
+
+ );
+};
+
+export default App;
diff --git a/src/components/IconGrid/IconGrid.css b/src/components/IconGrid/IconGrid.css
new file mode 100644
index 0000000..5049591
--- /dev/null
+++ b/src/components/IconGrid/IconGrid.css
@@ -0,0 +1,16 @@
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+}
+
+.grid-item {
+ margin: 4px;
+ background-color: aquamarine;
+ filter: grayscale(100%);
+ transition: 0.5s ease;
+}
+
+.grid-item:hover {
+ filter: grayscale(0%);
+ transition: 0.5s ease;
+}
diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx
new file mode 100644
index 0000000..ca492a9
--- /dev/null
+++ b/src/components/IconGrid/IconGrid.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { useRecoilValue } from "recoil";
+
+import { filteredQueryResultsSelector } from "../../state/selectors";
+import "./IconGrid.css";
+
+type IconGridProps = {};
+
+const IconGrid: React.FC = () => {
+ const filteredQueryResults = useRecoilValue(filteredQueryResultsSelector);
+
+ return (
+
+ {filteredQueryResults.map((icon) => (
+
+

+
{icon.name}
+
+ ))}
+
+ );
+};
+
+export default IconGrid;
diff --git a/src/components/IconSearch/IconSearch.tsx b/src/components/IconSearch/IconSearch.tsx
new file mode 100644
index 0000000..b0ef4fd
--- /dev/null
+++ b/src/components/IconSearch/IconSearch.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import { useRecoilState } from "recoil";
+
+import { searchQueryAtom, styleQueryAtom } from "../../state/atoms";
+import { IconFillStyle } from "../../lib/Icon";
+
+type IconSearchProps = {};
+
+const IconSearch: React.FC = () => {
+ const [query, setQuery] = useRecoilState(searchQueryAtom);
+ const [style, setStyle] = useRecoilState(styleQueryAtom);
+
+ const handleSearchChange = (event: React.ChangeEvent) => {
+ setQuery(event.target.value);
+ };
+
+ const handleStyleChange = (event: React.ChangeEvent) => {
+ setStyle(event.target.value as IconFillStyle);
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default IconSearch;
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..3898f18
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,3 @@
+export { default as App } from "./App/App";
+export { default as IconGrid } from "./IconGrid/IconGrid";
+export { default as IconSearch } from "./IconSearch/IconSearch";
\ No newline at end of file
diff --git a/src/data/iconList.ts b/src/data/iconList.ts
new file mode 100644
index 0000000..6437d62
--- /dev/null
+++ b/src/data/iconList.ts
@@ -0,0 +1,94 @@
+import { Icon, IconCategory, IconFillStyle } from "../lib/Icon";
+
+export const ICON_LIST: Icon[] = [
+ {
+ name: "arrow-up",
+ style: { type: IconFillStyle.LINE, weight: "light" },
+ categories: [
+ IconCategory.DESIGN,
+ IconCategory.EDITOR,
+ IconCategory.SYSTEM,
+ IconCategory.OTHER,
+ ],
+ tags: ["point", "pointer", "direction"],
+ },
+ {
+ name: "arrow-down",
+ style: { type: IconFillStyle.LINE, weight: "light" },
+ categories: [
+ IconCategory.DESIGN,
+ IconCategory.EDITOR,
+ IconCategory.SYSTEM,
+ IconCategory.OTHER,
+ ],
+ tags: ["point", "pointer", "direction"],
+ },
+ {
+ name: "arrow-left",
+ style: { type: IconFillStyle.LINE, weight: "light" },
+ categories: [
+ IconCategory.DESIGN,
+ IconCategory.EDITOR,
+ IconCategory.SYSTEM,
+ IconCategory.OTHER,
+ ],
+ tags: ["point", "pointer", "direction"],
+ },
+ {
+ name: "arrow-right",
+ style: { type: IconFillStyle.LINE, weight: "light" },
+ categories: [
+ IconCategory.DESIGN,
+ IconCategory.EDITOR,
+ IconCategory.SYSTEM,
+ IconCategory.OTHER,
+ ],
+ tags: ["point", "pointer", "direction"],
+ },
+ {
+ name: "house",
+ style: { type: IconFillStyle.FILL },
+ categories: [IconCategory.MAP, IconCategory.OTHER],
+ tags: ["building", "home", "place", "apartment"],
+ },
+ {
+ name: "hospital",
+ style: { type: IconFillStyle.FILL },
+ categories: [IconCategory.MAP, IconCategory.HEALTH, IconCategory.OTHER],
+ tags: ["building", "doctor", "place", "treatment"],
+ },
+ {
+ name: "mail",
+ style: { type: IconFillStyle.FILL },
+ categories: [IconCategory.BUSINESS, IconCategory.SYSTEM],
+ tags: ["email", "letter", "message", "messaging", "send"],
+ },
+ {
+ name: "mail",
+ style: { type: IconFillStyle.DUOTONE },
+ categories: [
+ IconCategory.BUSINESS,
+ IconCategory.COMMUNICATION,
+ IconCategory.SYSTEM,
+ ],
+ tags: ["email", "letter", "message", "messaging", "send"],
+ },
+ {
+ name: "chat",
+ style: { type: IconFillStyle.DUOTONE },
+ categories: [IconCategory.COMMUNICATION, IconCategory.SYSTEM],
+ tags: ["message", "messaging", "send"],
+ },
+ {
+ name: "chat",
+ style: { type: IconFillStyle.FILL },
+ categories: [IconCategory.COMMUNICATION, IconCategory.SYSTEM],
+ tags: ["message", "messaging", "send"],
+ },
+ // {
+ // name: "",
+ // style: IconFillStyle.FILL,
+ // categories: [],
+ // tags: [],
+ // },
+];
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index ec2585e..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/src/index.tsx b/src/index.tsx
index f5185c1..f11c67b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,14 +1,16 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import * as serviceWorker from './serviceWorker';
+import React from "react";
+import ReactDOM from "react-dom";
+import * as serviceWorker from "./serviceWorker";
+import { App } from "./components";
+import { RecoilRoot } from "recoil";
ReactDOM.render(
-
+
+
+
,
- document.getElementById('root')
+ document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
diff --git a/src/lib/Icon.ts b/src/lib/Icon.ts
new file mode 100644
index 0000000..98abc16
--- /dev/null
+++ b/src/lib/Icon.ts
@@ -0,0 +1,37 @@
+export interface IconStyle {
+ type: IconFillStyle;
+ weight?: "light" | "regular" | "medium" | "bold";
+}
+
+export enum IconFillStyle {
+ LINE = "line",
+ FILL = "fill",
+ DUOTONE = "duotone",
+}
+
+export enum IconCategory {
+ BRAND = "Brand",
+ BUSINESS = "Business",
+ COMMUNICATION = "Communication",
+ DESIGN = "Design",
+ DEVELOPMENT = "Development",
+ DEVICE = "Device",
+ DOCUMENT = "Document",
+ EDITOR = "Editor",
+ FINANCE = "Finance",
+ HEALTH = "Health & Medical",
+ LOGOS = "Logos",
+ MAP = "Map",
+ MEDIA = "Media",
+ SYSTEM = "System",
+ USERS = "Users",
+ WEATHER = "Weather",
+ OTHER = "Other",
+}
+
+export interface Icon {
+ name: string;
+ style: IconStyle;
+ categories: IconCategory[];
+ tags: string[];
+}
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 6b60c10..0000000
--- a/src/logo.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/src/state/atoms.ts b/src/state/atoms.ts
new file mode 100644
index 0000000..3ce64a9
--- /dev/null
+++ b/src/state/atoms.ts
@@ -0,0 +1,21 @@
+import { atom } from "recoil";
+import { IconFillStyle } from "../lib/Icon";
+
+/**
+ * ATOM
+ * An atom represents a piece of state. Atoms can be read from and written to from any component.
+ * Components that read the value of an atom are implicitly subscribed to that atom, so any atom
+ * updates will result in a re-render of all components subscribed to that atom:
+ */
+
+export type IconStyleQuery = IconFillStyle | null | undefined;
+
+export const searchQueryAtom = atom({
+ key: "searchQueryAtom",
+ default: "",
+});
+
+export const styleQueryAtom = atom({
+ key: "styleQueryAtom",
+ default: undefined,
+});
diff --git a/src/state/selectors.ts b/src/state/selectors.ts
new file mode 100644
index 0000000..098a550
--- /dev/null
+++ b/src/state/selectors.ts
@@ -0,0 +1,41 @@
+import { selector } from "recoil";
+import { searchQueryAtom, styleQueryAtom, IconStyleQuery } from "./atoms";
+import { ICON_LIST as list } from "../data/iconList";
+import { Icon } from "../lib/Icon";
+
+/**
+ * SELECTOR
+ * A selector represents a piece of derived state. Derived state is a transformation of state.
+ * You can think of derived state as the output of passing state to a pure function that
+ * modifies the given state in some way:
+ */
+
+const isQueryMatch = (icon: Icon, query: string): boolean => {
+ return (
+ icon.name.includes(query) ||
+ icon.tags.some((tag) => tag.toLowerCase().includes(query)) ||
+ icon.categories.some((category) =>
+ category.toLowerCase().includes(query)
+ ) ||
+ icon.style.type.toString().includes(query) ||
+ !!icon.style.weight?.includes(query)
+ );
+};
+
+const isStyleMatch = (icon: Icon, style: IconStyleQuery): boolean => {
+ return !style || icon.style.type === style;
+};
+
+export const filteredQueryResultsSelector = selector({
+ key: "filteredQueryResultsSelector",
+ get: ({ get }) => {
+ const query = get(searchQueryAtom).trim().toLowerCase();
+ const style = get(styleQueryAtom);
+
+ if (!query && !style) return list;
+
+ return list.filter((icon) => {
+ return isStyleMatch(icon, style) && isQueryMatch(icon, query);
+ });
+ },
+});
diff --git a/tsconfig.json b/tsconfig.json
index f2850b7..fb879e0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,10 +16,21 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
- "noEmit": true,
- "jsx": "react"
+ "jsx": "react",
+ "sourceMap": true,
+ "declaration": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "experimentalDecorators": true,
+ "incremental": true,
+ "noFallthroughCasesInSwitch": true,
+ "noEmit": true
},
"include": [
"src"
+ ],
+ "exclude": [
+ "node_modules",
+ "build"
]
}
diff --git a/yarn.lock b/yarn.lock
index f764a43..319656b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8770,6 +8770,11 @@ realpath-native@^1.1.0:
dependencies:
util.promisify "^1.0.0"
+recoil@^0.0.10:
+ version "0.0.10"
+ resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.0.10.tgz#679ab22306f559f8a63c46fd5ff5241539f9248f"
+ integrity sha512-+9gRqehw3yKETmoZbhSnWu4GO10HDb5xYf1CjLF1oXGK2uT6GX5Lu9mfTXwjxV/jXxEKx8MIRUUbgPxvbJ8SEw==
+
recursive-readdir@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"