diff --git a/README.md b/README.md index 64e343e..c890e03 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,64 @@ -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +# Phosphor Icons -## Available Scripts +Phosphor is a kickass and dead-simple set of open-source icons for web and digital media. We aim to provide variety, consistency, and above all, ease-of-use for digital content creators of all kinds. -In the project directory, you can run: +## For developers -### `yarn start` +Phosphor is available as an icon font and a React package, which can be sourced from NPM or from a CDN. -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +### Vanilla JS -The page will reload if you make edits.
-You will also see any lint errors in the console. +- **This seems familiar...** – Using Phosphor in your web project might seem familiar. We use a similar approach as many other icon sets out there, providing icons as a webfont that uses Unicode's Private Use Area character codes to map normally non-rendering characters to icons. But you don't need to know that. All you need to do is source the stylesheet, and drop in an icon: -### `yarn test` +```html + + + + + + + + + + + +``` -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +- **Whatchacallit?** – We use a straightforward and semantic naming scheme that may mean only changing a few letters when switching from other icon sets. But don't switch on our account, there are some excellent sets out there! +- **That's it?** – Yep. That's it. -### `yarn build` +### React -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. +- **Flex or flow** – Phosphor's intuitive but powerful API can style the `color`, `size`, and `weight` of an icon with a few keystrokes, or directly manipulate the SVG at runtime through render props to do some amazing things! Check out some examples on [Github](https://github.com/rektdeckard/phosphor-react). -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! +```jsx +import React from "react"; +import ReactDOM from "react-dom"; +import { Smiley, Heart, Horse } from "phosphor-react"; -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +const App = () => { + return ( + <> + + + + + ); +}; -### `yarn eject` +ReactDOM.render(, document.getElementById("root")); +``` -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +- **Light as a Feather** – Supports tree-shaking, so your bundle only includes code for the icons you use. +- **Familiar** – Icon Components are a thin wrapper around SVG elements, so feel free to add your own inline `style` objects, `onClick` handler functions, and a multitude of other props you're used to using on React Elements. -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +## For designers -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +### Raw Assets +- **SVGs** – Grab our individual icon SVGs, in both minified and original formats retaining design-time detail. +- **Icon Font** – Use the icons as you would text, in applications where full-fledged graphical elements are undesirable. -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). +### Source Files +- **Sketch** +- **Illustrator** +- **Figma** diff --git a/package.json b/package.json index aff79d0..48192fe 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react-dom": "^16.13.1", "react-list": "^0.8.15", "react-scripts": "3.4.1", + "react-use": "^15.3.2", "react-virtual": "^2.2.1", "react-virtualized": "^9.21.2", "recoil": "^0.0.10", diff --git a/src/components/ColorInput/ColorInput.tsx b/src/components/ColorInput/ColorInput.tsx index 30f4ee5..25d4e65 100644 --- a/src/components/ColorInput/ColorInput.tsx +++ b/src/components/ColorInput/ColorInput.tsx @@ -17,11 +17,9 @@ const ColorInput: React.FC = () => { return (
- = () => {

Phosphor Icons

- + + + +
); diff --git a/src/components/IconGrid/IconGrid.css b/src/components/IconGrid/IconGrid.css index e9f201b..5cc0bbc 100644 --- a/src/components/IconGrid/IconGrid.css +++ b/src/components/IconGrid/IconGrid.css @@ -1,21 +1,50 @@ .grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + /* grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); */ + + /* display: grid; */ + /* grid-template-columns: repeat(auto-fill, 160px); + grid-gap: 10px; + grid-auto-rows: minmax(160px, auto); */ + + display: flex; + flex-flow: row wrap; + justify-content: space-between; + margin: 16px; + /* min-height: 100vh; */ } .grid-item { display: flex; + width: 160px; + height: 160px; flex-direction: column; align-items: center; justify-content: center; margin: 4px; - border-radius: 8px; - background-color: white; - /* transition: background-color 0.5s ease; */ + border-radius: 16px; + user-select: none; + cursor: pointer; } -.grid-item:hover { - /* background-color: aquamarine; */ - /* transition: background-color 0.5s ease; */ +.grid-item:focus { + outline: none; +} + +.info-box { + display: flex; + margin: 4px; + padding: 16px; + width: 100%; + height: 0px; + border-radius: 16px; + box-shadow: 0 0 0 2px rgb(0, 0, 0); +} + +.empty-list { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; } diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx index 97e6753..51c20c8 100644 --- a/src/components/IconGrid/IconGrid.tsx +++ b/src/components/IconGrid/IconGrid.tsx @@ -1,46 +1,73 @@ -import React from "react"; +import React, { useRef, useEffect } from "react"; import { useRecoilValue } from "recoil"; -import { motion } from "framer-motion"; +import { motion, useAnimation } from "framer-motion"; +import { useWindowSize } from "react-use"; import { filteredQueryResultsSelector } from "../../state/selectors"; -import { iconColorAtom, iconSizeAtom, styleQueryAtom } from "../../state/atoms"; +import { + iconColorAtom, + iconSizeAtom, + styleQueryAtom, + searchQueryAtom, +} from "../../state/atoms"; import "./IconGrid.css"; +import GridItem from "./IconGridItem"; +import { WarningTriangle, IconProps } from "phosphor-react"; + type IconGridProps = {}; -// const variants = { -// open: { opacity: 1, x: 0 }, -// closed: { opacity: 0, x: "-100%" }, -// } - -const whileHover = { - boxShadow: "0 0 0 2px rgb(0, 0, 0)", - // scale: 1.2, -}; - -const transition = { duration: 0.2 }; - -const IconGrid: React.FC = () => { +const IconGridAnimated: React.FC = () => { const weight = useRecoilValue(styleQueryAtom); - const color = useRecoilValue(iconColorAtom); + const query = useRecoilValue(searchQueryAtom); const size = useRecoilValue(iconSizeAtom); + const color = useRecoilValue(iconColorAtom); + const iconProps: IconProps = { weight, color, size }; + + const { width } = useWindowSize(); + const spans = Math.floor((width - 32) / 172); + const filteredQueryResults = useRecoilValue(filteredQueryResultsSelector); + const originOffset = useRef({ top: 0, left: 0 }); + const controls = useAnimation(); + + useEffect(() => { + controls.start("visible"); + }, [controls, filteredQueryResults]); + + if (!filteredQueryResults.length) + return ( + + +

{`No results for '${query}'`}

+
+ ); + return ( - - {filteredQueryResults.map(({ name, Icon }) => ( - - -

{name}

-
+ + {filteredQueryResults.map((iconEntry, i) => ( + ))} ); }; -export default IconGrid; +export default IconGridAnimated; diff --git a/src/components/IconGrid/IconGridItem.tsx b/src/components/IconGrid/IconGridItem.tsx new file mode 100644 index 0000000..cf61f1c --- /dev/null +++ b/src/components/IconGrid/IconGridItem.tsx @@ -0,0 +1,130 @@ +import React, { + useRef, + useLayoutEffect, + useEffect, + MutableRefObject, +} from "react"; +import { useRecoilState } from "recoil"; +import { motion, AnimatePresence } from "framer-motion"; + +import { iconPreviewOpenAtom } from "../../state/atoms"; +import { IconProps, Icon } from "phosphor-react"; + +interface IconGridItemProps extends IconProps { + index: number; + name: string; + Icon: Icon; + originOffset: MutableRefObject<{ top: number; left: number }>; + spans: number; +} + +const itemVariants = { + hidden: { opacity: 0 }, + visible: (delayRef: any) => ({ + opacity: 1, + transition: { delay: delayRef.current }, + }), +}; +const whileHover = { boxShadow: "0 0 0 2px rgb(0, 0, 0)" }; +const whileTap = { boxShadow: "0 0 0 4px rgb(139, 0, 139)" }; +const transition = { duration: 0.2 }; +const originIndex = 0; +const delayPerPixel = 0.0004; + +const infoVariants = { + open: { opacity: 1, height: 176, marginTop: 4, marginBottom: 4, padding: 16 }, + collapsed: { + opacity: 0, + height: 0, + marginTop: 0, + marginBottom: 0, + padding: 0, + }, +}; + +const IconGridItem: React.FC = (props) => { + const { index, spans, originOffset, name, Icon, ...iconProps } = props; + const [open, setOpen] = useRecoilState(iconPreviewOpenAtom); + const delayRef = useRef(0); + const offset = useRef({ top: 0, left: 0 }); + const ref = useRef(); + + // The measurement for all elements happens in the layoutEffect cycle + // This ensures that when we calculate distance in the effect cycle + // all elements have already been measured + useLayoutEffect(() => { + const element = ref.current; + if (!element) return; + + offset.current = { + top: element.offsetTop, + left: element.offsetLeft, + }; + + if (index === originIndex) { + originOffset.current = offset.current; + } + }, []); + + useEffect(() => { + const dx = Math.abs(offset.current.left - originOffset.current.left); + const dy = Math.abs(offset.current.top - originOffset.current.top); + const d = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + delayRef.current = d * delayPerPixel; + }, []); + + return ( + <> + + setOpen((openName) => (name === openName ? false : name)) + } + > + +

{name}

+
+ + {open === name && } + + + ); +}; + +const InfoPanel: React.FC = (props) => { + const { index, spans, name, Icon, color, weight } = props; + return ( + +
+ +

{name}

+
+
+ HTML +
{``}
+ React +
{`<${Icon.displayName} ${
+          weight === "regular" ? "" : `weight="${weight}"`
+        }/>`}
+
+
+ ); +}; + +export default IconGridItem; diff --git a/src/components/IconGrid/IconGridStatic.tsx b/src/components/IconGrid/IconGridStatic.tsx new file mode 100644 index 0000000..5a26ecf --- /dev/null +++ b/src/components/IconGrid/IconGridStatic.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { useRecoilValue } from "recoil"; +import { motion } from "framer-motion"; + +import { filteredQueryResultsSelector } from "../../state/selectors"; +import { iconColorAtom, iconSizeAtom, styleQueryAtom } from "../../state/atoms"; +import "./IconGrid.css"; + +type IconGridProps = {}; + +// const variants = { +// open: { opacity: 1, x: 0 }, +// closed: { opacity: 0, x: "-100%" }, +// } + +const whileHover = { boxShadow: "0 0 0 2px rgb(0, 0, 0)" }; +const whileTap = { boxShadow: "0 0 0 4px rgb(139, 0, 139)" } + +const transition = { duration: 0.2 }; + +const IconGrid: React.FC = () => { + const weight = useRecoilValue(styleQueryAtom); + const color = useRecoilValue(iconColorAtom); + const size = useRecoilValue(iconSizeAtom); + const filteredQueryResults = useRecoilValue(filteredQueryResultsSelector); + + return ( + + {filteredQueryResults.map(({ name, Icon }) => ( + + +

{name}

+
+ ))} +
+ ); +}; + +export default IconGrid; diff --git a/src/components/SearchInput/SearchInput.css b/src/components/SearchInput/SearchInput.css index 0590393..84f6507 100644 --- a/src/components/SearchInput/SearchInput.css +++ b/src/components/SearchInput/SearchInput.css @@ -2,7 +2,7 @@ position: relative; display: flex; align-items: center; - width: 250px; + flex: 2; margin: 4px; padding: 8px 16px; border: 1px solid black; diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx index 6673bd6..106e809 100644 --- a/src/components/SearchInput/SearchInput.tsx +++ b/src/components/SearchInput/SearchInput.tsx @@ -16,11 +16,9 @@ const SearchInput: React.FC = () => { return (
- = () => { return (
-