mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-10 15:35:35 +00:00
feat: add some animation
This commit is contained in:
39
package-lock.json
generated
39
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"@prisma/client": "^4.14.0",
|
"@prisma/client": "^4.14.0",
|
||||||
"@svgr/webpack": "^8.0.1",
|
"@svgr/webpack": "^8.0.1",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
"framer-motion": "^10.12.12",
|
||||||
"next": "^13.4.2",
|
"next": "^13.4.2",
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.0.0",
|
||||||
@@ -1763,6 +1764,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
|
||||||
"integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
|
"integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "0.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||||
|
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "0.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@emotion/memoize": {
|
"node_modules/@emotion/memoize": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
|
||||||
@@ -4280,6 +4296,29 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "10.12.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.12.tgz",
|
||||||
|
"integrity": "sha512-DDCqp60U6hR7aUrXj/BXc/t0Sd/U4ep6w/NZQkw898K+u7s+Vv/P8yxq4WTNA86kU9QCsqOgn1Qhz2DpYK0Oag==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "^0.8.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"@prisma/client": "^4.14.0",
|
"@prisma/client": "^4.14.0",
|
||||||
"@svgr/webpack": "^8.0.1",
|
"@svgr/webpack": "^8.0.1",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
"framer-motion": "^10.12.12",
|
||||||
"next": "^13.4.2",
|
"next": "^13.4.2",
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.0.0",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import LinkTag from "next/link";
|
import LinkTag from "next/link";
|
||||||
import { AiFillStar } from "react-icons/ai";
|
import { AiFillStar } from "react-icons/ai";
|
||||||
|
|
||||||
@@ -14,9 +15,11 @@ import styles from "./links.module.scss";
|
|||||||
export default function LinkItem({
|
export default function LinkItem({
|
||||||
link,
|
link,
|
||||||
toggleFavorite,
|
toggleFavorite,
|
||||||
|
index,
|
||||||
}: {
|
}: {
|
||||||
link: Link;
|
link: Link;
|
||||||
toggleFavorite: (linkId: Link["id"]) => void;
|
toggleFavorite: (linkId: Link["id"]) => void;
|
||||||
|
index: number;
|
||||||
}) {
|
}) {
|
||||||
const { id, name, url, favorite } = link;
|
const { id, name, url, favorite } = link;
|
||||||
const onFavorite = () => {
|
const onFavorite = () => {
|
||||||
@@ -33,7 +36,18 @@ export default function LinkItem({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={styles["link"]} key={id}>
|
<motion.li
|
||||||
|
className={styles["link"]}
|
||||||
|
key={id}
|
||||||
|
initial={{ x: -30, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 260,
|
||||||
|
damping: 20,
|
||||||
|
delay: index * 0.05,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LinkFavicon url={url} />
|
<LinkFavicon url={url} />
|
||||||
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
<LinkTag href={url} target={"_blank"} rel={"noreferrer"}>
|
||||||
<span className={styles["link-name"]}>
|
<span className={styles["link-name"]}>
|
||||||
@@ -46,7 +60,7 @@ export default function LinkItem({
|
|||||||
<EditItem type="link" id={id} />
|
<EditItem type="link" id={id} />
|
||||||
<RemoveItem type="link" id={id} />
|
<RemoveItem type="link" id={id} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</motion.li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import EditItem from "components/QuickActions/EditItem";
|
|||||||
import RemoveItem from "components/QuickActions/RemoveItem";
|
import RemoveItem from "components/QuickActions/RemoveItem";
|
||||||
import LinkItem from "./LinkItem";
|
import LinkItem from "./LinkItem";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import styles from "./links.module.scss";
|
import styles from "./links.module.scss";
|
||||||
|
|
||||||
export default function Links({
|
export default function Links({
|
||||||
@@ -40,16 +41,32 @@ export default function Links({
|
|||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
{links.length !== 0 ? (
|
{links.length !== 0 ? (
|
||||||
<ul className={styles["links"]} key={Math.random()}>
|
<ul className={styles["links"]}>
|
||||||
{links.map((link, key) => (
|
{links.map((link, index) => (
|
||||||
<LinkItem key={key} link={link} toggleFavorite={toggleFavorite} />
|
<LinkItem
|
||||||
|
link={link}
|
||||||
|
toggleFavorite={toggleFavorite}
|
||||||
|
index={index}
|
||||||
|
key={link.id}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles["no-link"]}>
|
<div className={styles["no-link"]}>
|
||||||
<p>
|
<AnimatePresence>
|
||||||
|
<motion.p
|
||||||
|
key={Math.random()}
|
||||||
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 260,
|
||||||
|
damping: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Aucun lien pour <b>{name}</b>
|
Aucun lien pour <b>{name}</b>
|
||||||
</p>
|
</motion.p>
|
||||||
|
</AnimatePresence>
|
||||||
<LinkTag href={`/link/create?categoryId=${id}`}>
|
<LinkTag href={`/link/create?categoryId=${id}`}>
|
||||||
Créer un lien
|
Créer un lien
|
||||||
</LinkTag>
|
</LinkTag>
|
||||||
|
|||||||
@@ -71,7 +71,6 @@
|
|||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
animation: fadein 0.3s both; // bug on drag start
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
@@ -89,7 +88,6 @@
|
|||||||
outline: 3px solid transparent;
|
outline: 3px solid transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ export default function Categories({
|
|||||||
<div className={styles["categories"]}>
|
<div className={styles["categories"]}>
|
||||||
<h4>Catégories • {linksCount}</h4>
|
<h4>Catégories • {linksCount}</h4>
|
||||||
<ul className={styles["items"]}>
|
<ul className={styles["items"]}>
|
||||||
{categories.map((category, key) => (
|
{categories.map((category, index) => (
|
||||||
<CategoryItem
|
<CategoryItem
|
||||||
category={category}
|
category={category}
|
||||||
categoryActive={categoryActive}
|
categoryActive={categoryActive}
|
||||||
handleSelectCategory={handleSelectCategory}
|
handleSelectCategory={handleSelectCategory}
|
||||||
key={key}
|
key={category.id}
|
||||||
|
index={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import { AiFillFolderOpen, AiOutlineFolder } from "react-icons/ai";
|
|||||||
|
|
||||||
import { Category } from "types";
|
import { Category } from "types";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import styles from "./categories.module.scss";
|
import styles from "./categories.module.scss";
|
||||||
|
|
||||||
interface CategoryItemProps {
|
interface CategoryItemProps {
|
||||||
category: Category;
|
category: Category;
|
||||||
categoryActive: Category;
|
categoryActive: Category;
|
||||||
handleSelectCategory: (category: Category) => void;
|
handleSelectCategory: (category: Category) => void;
|
||||||
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CategoryItem({
|
export default function CategoryItem({
|
||||||
category,
|
category,
|
||||||
categoryActive,
|
categoryActive,
|
||||||
handleSelectCategory,
|
handleSelectCategory,
|
||||||
|
index,
|
||||||
}: CategoryItemProps): JSX.Element {
|
}: CategoryItemProps): JSX.Element {
|
||||||
const ref = useRef<HTMLLIElement>();
|
const ref = useRef<HTMLLIElement>();
|
||||||
const className = `${styles["item"]} ${
|
const className = `${styles["item"]} ${
|
||||||
@@ -29,11 +32,25 @@ export default function CategoryItem({
|
|||||||
}, [category.id, categoryActive.id]);
|
}, [category.id, categoryActive.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<motion.li
|
||||||
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 260,
|
||||||
|
damping: 20,
|
||||||
|
delay: index * 0.025,
|
||||||
|
duration: 200,
|
||||||
|
}}
|
||||||
className={className}
|
className={className}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ display: "flex", alignItems: "center", gap: ".25em" }}
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: ".25em",
|
||||||
|
transition: "none",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{category.id === categoryActive.id ? (
|
{category.id === categoryActive.id ? (
|
||||||
<AiFillFolderOpen size={24} />
|
<AiFillFolderOpen size={24} />
|
||||||
@@ -45,6 +62,6 @@ export default function CategoryItem({
|
|||||||
<span className={styles["name"]}>{category.name}</span>
|
<span className={styles["name"]}>{category.name}</span>
|
||||||
<span className={styles["links-count"]}>— {category.links.length}</span>
|
<span className={styles["links-count"]}>— {category.links.length}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</motion.li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import SearchModal from "components/SearchModal/SearchModal";
|
|||||||
import SideMenu from "components/SideMenu/SideMenu";
|
import SideMenu from "components/SideMenu/SideMenu";
|
||||||
|
|
||||||
import * as Keys from "constants/keys";
|
import * as Keys from "constants/keys";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import { Category, Link, SearchItem } from "types";
|
import { Category, Link, SearchItem } from "types";
|
||||||
import { prisma } from "utils/back";
|
import { prisma } from "utils/back";
|
||||||
import { BuildCategory } from "utils/front";
|
import { BuildCategory } from "utils/front";
|
||||||
@@ -155,7 +156,16 @@ function Home(props: HomeProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<motion.div
|
||||||
|
className="App"
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 260,
|
||||||
|
damping: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SideMenu
|
<SideMenu
|
||||||
categories={categories}
|
categories={categories}
|
||||||
favorites={favorites}
|
favorites={favorites}
|
||||||
@@ -173,7 +183,7 @@ function Home(props: HomeProps) {
|
|||||||
handleSelectCategory={handleSelectCategory}
|
handleSelectCategory={handleSelectCategory}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ body {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
animation: fadein 250ms both;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
Reference in New Issue
Block a user