IconGrid: massive refactor including component extraction

This patch extracts InfoPanel from IconGridItem, adds style tweaks to
match the spec, and in general reduces prop-drilling by tapping into
recoil state for most config, and making use of IconContext to style
member icons where appropriate :)
This commit is contained in:
rektdeckard
2020-07-29 12:27:50 -04:00
parent c269343014
commit 6941250d10
4 changed files with 283 additions and 94 deletions

View File

@@ -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<IconGridItemProps> = (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<number>(0);
const offset = useRef({ top: 0, left: 0 });
const ref = useRef<any>();
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<IconGridItemProps> = (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 (
<>
<motion.div
className="grid-item"
ref={ref}
style={{ order: index }}
custom={delayRef}
key={name}
ref={ref}
tabIndex={0}
// onFocus={console.log}
style={{
order: index,
backgroundColor: isDark
? "rgba(73, 70, 80, 0)"
: "rgba(246, 242, 243, 0)",
}}
custom={delayRef}
whileHover={whileHover}
whileTap={whileTap}
transition={transition}
variants={itemVariants}
onClick={() =>
setOpen((openName) => (name === openName ? false : name))
}
onKeyPress={(e) => e.key === "Enter" && handleOpen()}
onClick={handleOpen}
>
<Icon {...iconProps} />
<Icon />
<p>{name}</p>
</motion.div>
<AnimatePresence initial={false}>
{open === name && <InfoPanel {...props} />}
{isOpen && <InfoPanel {...props} />}
</AnimatePresence>
</>
);
};
const InfoPanel: React.FC<IconGridItemProps> = (props) => {
const { index, spans, name, Icon, color, weight } = props;
return (
<motion.section
className="info-box"
animate="open"
exit="collapsed"
variants={infoVariants}
style={{ order: index + (spans - (index % spans)) }}
>
<div style={{ height: "100%" }}>
<Icon color={color} weight={weight} size={128} />
<p>{name}</p>
</div>
<div style={{ flex: 1, padding: 32 }}>
HTML
<pre>{`<i class="ph-${name}${
weight === "regular" ? "" : `-${weight}`
}"></i>`}</pre>
React
<pre>{`<${Icon.displayName} ${
weight === "regular" ? "" : `weight="${weight}"`
}/>`}</pre>
</div>
</motion.section>
);
};
export default IconGridItem;