From 3a9cf5dc990a75e98d92bf0955617614ce88248c Mon Sep 17 00:00:00 2001 From: Tobias Fried Date: Tue, 14 Jul 2020 17:41:45 -0400 Subject: [PATCH] Initial commit --- package.json | 4 +- src/App.tsx | 26 ------- src/{ => components/App}/App.css | 2 +- src/{ => components/App}/App.test.tsx | 0 src/components/App/App.tsx | 24 ++++++ src/components/IconGrid/IconGrid.css | 16 ++++ src/components/IconGrid/IconGrid.tsx | 28 +++++++ src/components/IconSearch/IconSearch.tsx | 34 +++++++++ src/components/index.ts | 3 + src/data/iconList.ts | 94 ++++++++++++++++++++++++ src/index.css | 13 ---- src/index.tsx | 16 ++-- src/lib/Icon.ts | 37 ++++++++++ src/logo.svg | 7 -- src/state/atoms.ts | 21 ++++++ src/state/selectors.ts | 41 +++++++++++ tsconfig.json | 15 +++- yarn.lock | 5 ++ 18 files changed, 329 insertions(+), 57 deletions(-) delete mode 100644 src/App.tsx rename src/{ => components/App}/App.css (96%) rename src/{ => components/App}/App.test.tsx (100%) create mode 100644 src/components/App/App.tsx create mode 100644 src/components/IconGrid/IconGrid.css create mode 100644 src/components/IconGrid/IconGrid.tsx create mode 100644 src/components/IconSearch/IconSearch.tsx create mode 100644 src/components/index.ts create mode 100644 src/data/iconList.ts delete mode 100644 src/index.css create mode 100644 src/lib/Icon.ts delete mode 100644 src/logo.svg create mode 100644 src/state/atoms.ts create mode 100644 src/state/selectors.ts 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 ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); -} - -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 ( +
+
+ Phosphor Icons{" "} + + on the play store + +
+ + +
+ ); +}; + +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} +
{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"