diff --git a/src/components/IconGrid/IconGrid.css b/src/components/IconGrid/IconGrid.css index 5cc0bbc..9a52800 100644 --- a/src/components/IconGrid/IconGrid.css +++ b/src/components/IconGrid/IconGrid.css @@ -1,21 +1,28 @@ -.grid { - /* grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); */ +.grid-container { + padding: 0px 16px 8px; + /* background-color: #35313D; + color: white; */ +} - /* display: grid; */ - /* grid-template-columns: repeat(auto-fill, 160px); +.grid { + /* display: grid; + grid-area: auto; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); grid-gap: 10px; - grid-auto-rows: minmax(160px, auto); */ + grid-auto-rows: 160px; + grid-auto-columns: auto; + max-width: 1120px; */ display: flex; flex-flow: row wrap; justify-content: space-between; - margin: 16px; - - /* min-height: 100vh; */ + max-width: 1120px; + margin: auto; } .grid-item { display: flex; + box-sizing: border-box; width: 160px; height: 160px; flex-direction: column; @@ -25,20 +32,82 @@ border-radius: 16px; user-select: none; cursor: pointer; + /* background-color: rgba(255, 255, 255, 0); */ } .grid-item:focus { outline: none; + box-shadow: 0 0 0 2px rgb(246, 242, 243); +} + +.grid-item p { + font-size: 12px; + line-height: 16px; + color: #7F7F7F; + margin-top: 12px; } .info-box { display: flex; - margin: 4px; - padding: 16px; width: 100%; height: 0px; + margin: 0px; + padding-right: 10%; border-radius: 16px; - box-shadow: 0 0 0 2px rgb(0, 0, 0); + overflow: hidden; +} + +.icon-preview { + height: 396px; + width: 30%; + display: flex; + text-align: center; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.icon-preview p { + margin: 12px 0 0; + font-size: 12px; + line-height: 16px; +} + +.icon-usage { + flex: 1; + padding: 56px 0px 56px; + /* display: flex; */ + /* flex-direction: column; */ + /* justify-content: flex-start; */ + /* vertical-align: middle; */ +} + +.snippet { + margin-bottom: 24px; +} +.snippet pre { + text-overflow: ellipsis; + /* overflow-x: auto; */ +} +.snippet button { + background-color: transparent; + margin: 0; + padding: 0; + height: 24px; + float: right; +} + +.button-row { + display: flex; +} + +.button-row button { + background-color: transparent; + font-size: 16px; + line-height: 24px; + margin: 0 48px 0 0; + padding: 0; + height: 48px; } .empty-list { diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx index 51c20c8..fe0f777 100644 --- a/src/components/IconGrid/IconGrid.tsx +++ b/src/components/IconGrid/IconGrid.tsx @@ -2,30 +2,30 @@ import React, { useRef, useEffect } from "react"; import { useRecoilValue } from "recoil"; import { motion, useAnimation } from "framer-motion"; import { useWindowSize } from "react-use"; +import TinyColor from "tinycolor2"; +import { IconContext, WarningTriangle } from "phosphor-react"; -import { filteredQueryResultsSelector } from "../../state/selectors"; import { - iconColorAtom, - iconSizeAtom, styleQueryAtom, + iconSizeAtom, + iconColorAtom, searchQueryAtom, } from "../../state/atoms"; -import "./IconGrid.css"; - +import { filteredQueryResultsSelector } from "../../state/selectors"; import GridItem from "./IconGridItem"; -import { WarningTriangle, IconProps } from "phosphor-react"; +import "./IconGrid.css"; type IconGridProps = {}; const IconGridAnimated: React.FC = () => { const weight = useRecoilValue(styleQueryAtom); - const query = useRecoilValue(searchQueryAtom); const size = useRecoilValue(iconSizeAtom); const color = useRecoilValue(iconColorAtom); - const iconProps: IconProps = { weight, color, size }; + const query = useRecoilValue(searchQueryAtom); + const isDark = TinyColor(color).isLight(); const { width } = useWindowSize(); - const spans = Math.floor((width - 32) / 172); + const spans = Math.floor(Math.min(width - 32, 1120) / 168); const filteredQueryResults = useRecoilValue(filteredQueryResultsSelector); @@ -50,23 +50,30 @@ const IconGridAnimated: React.FC = () => { ); return ( - - {filteredQueryResults.map((iconEntry, i) => ( - - ))} - + +
+ + {filteredQueryResults.map((iconEntry, index) => ( + + ))} + +
+
); }; diff --git a/src/components/IconGrid/IconGridItem.tsx b/src/components/IconGrid/IconGridItem.tsx index cf61f1c..32efca3 100644 --- a/src/components/IconGrid/IconGridItem.tsx +++ b/src/components/IconGrid/IconGridItem.tsx @@ -2,6 +2,7 @@ import React, { useRef, useLayoutEffect, useEffect, + useMemo, MutableRefObject, } from "react"; import { useRecoilState } from "recoil"; @@ -9,15 +10,22 @@ import { motion, AnimatePresence } from "framer-motion"; import { iconPreviewOpenAtom } from "../../state/atoms"; import { IconProps, Icon } from "phosphor-react"; +import InfoPanel from "./InfoPanel"; interface IconGridItemProps extends IconProps { index: number; + spans: number; + isDark: boolean; name: string; Icon: Icon; originOffset: MutableRefObject<{ top: number; left: number }>; - spans: number; } +const whileTap = { boxShadow: "0 0 0 4px rgb(73, 70, 80)" }; +const transition = { duration: 0.2 }; +const originIndex = 0; +const delayPerPixel = 0.0004; + const itemVariants = { hidden: { opacity: 0 }, visible: (delayRef: any) => ({ @@ -25,30 +33,24 @@ const itemVariants = { 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 { index, isDark, originOffset, name, Icon } = props; const [open, setOpen] = useRecoilState(iconPreviewOpenAtom); + const isOpen = open === name; const delayRef = useRef(0); const offset = useRef({ top: 0, left: 0 }); const ref = useRef(); + const handleOpen = () => setOpen(isOpen ? false : name); + + const whileHover = useMemo( + () => ({ + backgroundColor: isDark ? "rgb(73, 70, 80)" : "rgb(246, 242, 243)", + }), + [isDark] + ); + // 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 @@ -64,67 +66,44 @@ const IconGridItem: React.FC = (props) => { if (index === originIndex) { originOffset.current = offset.current; } - }, []); + }, [index, originOffset]); 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; - }, []); - + }, [originOffset]); return ( <> - setOpen((openName) => (name === openName ? false : name)) - } + onKeyPress={(e) => e.key === "Enter" && handleOpen()} + onClick={handleOpen} > - +

{name}

- {open === name && } + {isOpen && } ); }; -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/InfoPanel.tsx b/src/components/IconGrid/InfoPanel.tsx new file mode 100644 index 0000000..0339fde --- /dev/null +++ b/src/components/IconGrid/InfoPanel.tsx @@ -0,0 +1,134 @@ +import React, { useRef } from "react"; +import { useRecoilValue } from "recoil"; +import { motion } from "framer-motion"; +import { saveAs } from "file-saver"; + +import { styleQueryAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms"; +import { Icon, ArrowUpRightCircle, Copy } from "phosphor-react"; + +const infoVariants = { + open: { + opacity: 1, + height: 396, + margin: 4, + // transition: { stiffness: 600, damping: 32, duration: 0.2 }, + }, + collapsed: { + opacity: 0, + height: 0, + margin: 0, + // transition: { stiffness: 600, damping: 32, duration: 0.2 }, + }, +}; + +interface InfoPanelProps { + index: number; + spans: number; + isDark: boolean; + name: string; + Icon: Icon; +} + +const InfoPanel: React.FC = (props) => { + const { index, spans, isDark, name, Icon } = props; + const weight = useRecoilValue(styleQueryAtom); + const size = useRecoilValue(iconSizeAtom); + const color = useRecoilValue(iconColorAtom); + const ref = useRef(null); + + const htmlString = ``; + const reactString = `<${Icon.displayName} size={${size}} color="${color}" ${ + weight === "regular" ? "" : `weight="${weight}" ` + }/>`; + + const handleCopySnippet = (data: string) => { + data && navigator.clipboard.writeText(data); + }; + + const handleDownloadSVG = () => { + if (!ref.current?.outerHTML) return; + const blob = new Blob([ref.current.outerHTML]); + saveAs(blob, `${name}.svg`); + }; + + const handleCopySVG = () => { + ref.current && navigator.clipboard.writeText(ref.current.outerHTML); + }; + + return ( + +
+
+ +

{name}

+
+
+
+
+ HTML/CSS +
+            {htmlString}
+            
+          
+
+
+ React +
+            {reactString}
+            
+          
+
+
+ + +
+
+
+ ); +}; + +export default InfoPanel;