Skip to content
Merged
58 changes: 47 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"prepare": "husky"
},
"dependencies": {
"framer-motion": "^11.15.0",
"prismjs": "^1.29.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
73 changes: 49 additions & 24 deletions src/components/SnippetList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";

import { useAppContext } from "@contexts/AppContext";
Expand Down Expand Up @@ -31,31 +32,55 @@ const SnippetList = () => {

return (
<>
<ul role="list" className="snippets">
{fetchedSnippets.map((snippet, idx) => (
<li key={idx}>
<button
className="snippet | flow"
data-flow-space="sm"
onClick={() => handleOpenModal(snippet)}
<motion.ul role="list" className="snippets">
<AnimatePresence mode="popLayout">
{fetchedSnippets.map((snippet, idx) => (
<motion.li
key={idx}
initial={{ opacity: 0, y: 20 }}
animate={{
opacity: 1,
y: 0,
transition: {
delay: idx * 0.05,
duration: 0.2,
},
}}
exit={{
opacity: 0,
y: -20,
transition: {
delay: (fetchedSnippets.length - 1 - idx) * 0.01,
duration: 0.09,
},
}}
>
<div className="snippet__preview">
<img src={language.icon} alt={language.lang} />
</div>

<h3 className="snippet__title">{snippet.title}</h3>
</button>
</li>
))}
</ul>

{isModalOpen && snippet && (
<SnippetModal
snippet={snippet}
handleCloseModal={handleCloseModal}
language={language.lang}
/>
)}
<motion.button
className="snippet | flow"
data-flow-space="sm"
onClick={() => handleOpenModal(snippet)}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.98 }}
>
<div className="snippet__preview">
<img src={language.icon} alt={language.lang} />
</div>
<h3 className="snippet__title">{snippet.title}</h3>
</motion.button>
</motion.li>
))}
</AnimatePresence>
</motion.ul>

<AnimatePresence mode="wait">
{isModalOpen && snippet && (
<SnippetModal
snippet={snippet}
handleCloseModal={handleCloseModal}
language={language.lang}
/>
)}
</AnimatePresence>
</>
);
};
Expand Down
22 changes: 18 additions & 4 deletions src/components/SnippetModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { motion } from "framer-motion";
import React from "react";
import ReactDOM from "react-dom";

Expand Down Expand Up @@ -29,15 +30,28 @@ const SnippetModal: React.FC<Props> = ({
}

return ReactDOM.createPortal(
<div
<motion.div
key="modal-overlay"
className="modal-overlay"
onClick={(e) => {
if (e.target === e.currentTarget) {
handleCloseModal();
}
}}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<div className="modal | flow" data-flow-space="lg">
<motion.div
key="modal-content"
className="modal | flow"
data-flow-space="lg"
initial={{ scale: 0.8, opacity: 0, y: 20 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.8, opacity: 0, y: 20 }}
transition={{ type: "spring", duration: 0.5 }}
>
<div className="modal__header">
<h2 className="section-title">{snippet.title}</h2>
<Button isIcon={true} onClick={handleCloseModal}>
Expand Down Expand Up @@ -67,8 +81,8 @@ const SnippetModal: React.FC<Props> = ({
</li>
))}
</ul>
</div>
</div>,
</motion.div>
</motion.div>,
modalRoot
);
};
Expand Down