mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 00:44:14 +00:00
feat: add basic animations with Framer Motion
This commit is contained in:
parent
f2efe3c490
commit
fa7b90eeec
6 changed files with 104 additions and 56 deletions
42
package-lock.json
generated
42
package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
|||
"@types/react": "^18.2.25",
|
||||
"@types/react-dom": "^18.2.10",
|
||||
"astro": "^3.2.3",
|
||||
"framer-motion": "10.16.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "4.11.0",
|
||||
|
|
@ -1356,6 +1357,21 @@
|
|||
"postcss-selector-parser": "^6.0.13"
|
||||
}
|
||||
},
|
||||
"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/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/@esbuild/android-arm": {
|
||||
"version": "0.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz",
|
||||
|
|
@ -7181,6 +7197,29 @@
|
|||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "10.16.4",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.4.tgz",
|
||||
"integrity": "sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==",
|
||||
"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-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
|
|
@ -14815,8 +14854,7 @@
|
|||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"@types/react": "^18.2.25",
|
||||
"@types/react-dom": "^18.2.10",
|
||||
"astro": "^3.2.3",
|
||||
"framer-motion": "10.16.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "4.11.0",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
useRole,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import { useSoundStore } from '@/store';
|
||||
import { usePlay } from '@/contexts/play';
|
||||
|
|
@ -66,9 +67,10 @@ export function Buttons() {
|
|||
|
||||
return (
|
||||
<div className={styles.buttons}>
|
||||
<button
|
||||
<motion.button
|
||||
className={cn(styles.playButton, noSelected && styles.disabled)}
|
||||
disabled={noSelected}
|
||||
layout
|
||||
onClick={handleClick}
|
||||
>
|
||||
{isPlaying ? (
|
||||
|
|
@ -86,27 +88,37 @@ export function Buttons() {
|
|||
Play
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</motion.button>
|
||||
|
||||
<button
|
||||
disabled={noSelected && !hasHistory}
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps}
|
||||
aria-label={
|
||||
hasHistory ? 'Restore Unselected Sounds' : 'Unselect All Sounds'
|
||||
}
|
||||
className={cn(
|
||||
styles.smallButton,
|
||||
hasHistory ? styles.restore : styles.delete,
|
||||
noSelected && !hasHistory && styles.disabled,
|
||||
<AnimatePresence>
|
||||
{(!noSelected || hasHistory) && (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
>
|
||||
<button
|
||||
disabled={noSelected && !hasHistory}
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps}
|
||||
aria-label={
|
||||
hasHistory ? 'Restore Unselected Sounds' : 'Unselect All Sounds'
|
||||
}
|
||||
className={cn(
|
||||
styles.smallButton,
|
||||
hasHistory ? styles.restore : styles.delete,
|
||||
noSelected && !hasHistory && styles.disabled,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (hasHistory) restoreHistory();
|
||||
else if (!noSelected) unselectAll(true);
|
||||
}}
|
||||
>
|
||||
{hasHistory ? <BiUndo /> : <BiTrash />}
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
onClick={() => {
|
||||
if (hasHistory) restoreHistory();
|
||||
else if (!noSelected) unselectAll(true);
|
||||
}}
|
||||
>
|
||||
{hasHistory ? <BiUndo /> : <BiTrash />}
|
||||
</button>
|
||||
</AnimatePresence>
|
||||
|
||||
{isTooltipOpen && (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
.help {
|
||||
margin-top: 20px;
|
||||
color: var(--color-foreground-subtle);
|
||||
font-size: var(--font-sm);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { BiSolidHeart } from 'react-icons/bi/index';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
import { useFavoriteStore } from '@/store/favorite';
|
||||
import { useSoundStore } from '@/store/sound';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { StoreConsumer } from '../store-consumer';
|
||||
|
|
@ -13,13 +13,10 @@ import { PlayProvider } from '@/contexts/play';
|
|||
|
||||
import { sounds } from '@/data/sounds';
|
||||
|
||||
import styles from './categories.module.css';
|
||||
|
||||
export function Categories() {
|
||||
const categories = useMemo(() => sounds.categories, []);
|
||||
|
||||
const favorites = useFavoriteStore(useShallow(state => state.favorites));
|
||||
const noSelected = useSoundStore(state => state.noSelected());
|
||||
|
||||
const favoriteSounds = useMemo(() => {
|
||||
const favoriteSounds = categories
|
||||
|
|
@ -40,30 +37,29 @@ export function Categories() {
|
|||
<PlayProvider>
|
||||
<Container>
|
||||
<Buttons />
|
||||
|
||||
{noSelected && <p className={styles.help}>Select a sound to play!</p>}
|
||||
|
||||
<div>
|
||||
{!!favoriteSounds.length && (
|
||||
<Category
|
||||
functional={false}
|
||||
icon={<BiSolidHeart />}
|
||||
id="favorites"
|
||||
title="Favorites"
|
||||
sounds={
|
||||
favoriteSounds as Array<{
|
||||
src: string;
|
||||
label: string;
|
||||
id: string;
|
||||
icon: React.ReactNode;
|
||||
}>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<AnimatePresence initial={false}>
|
||||
{!!favoriteSounds.length && (
|
||||
<Category
|
||||
functional={false}
|
||||
icon={<BiSolidHeart />}
|
||||
id="favorites"
|
||||
title="Favorites"
|
||||
sounds={
|
||||
favoriteSounds as Array<{
|
||||
src: string;
|
||||
label: string;
|
||||
id: string;
|
||||
icon: React.ReactNode;
|
||||
}>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{categories.map(category => (
|
||||
<Category {...category} key={category.id} />
|
||||
))}
|
||||
{categories.map(category => (
|
||||
<Category {...category} key={category.id} />
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Container>
|
||||
</PlayProvider>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { motion } from 'framer-motion';
|
||||
|
||||
import { Sounds } from '@/components/sounds';
|
||||
|
||||
import styles from './category.module.css';
|
||||
|
|
@ -23,7 +25,12 @@ export function Category({
|
|||
title,
|
||||
}: CategoryProps) {
|
||||
return (
|
||||
<div className={styles.category}>
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
className={styles.category}
|
||||
initial={{ opacity: 0 }}
|
||||
layout
|
||||
>
|
||||
<div className={styles.iconContainer}>
|
||||
<div className={styles.tail} />
|
||||
<div className={styles.icon}>{icon}</div>
|
||||
|
|
@ -32,6 +39,6 @@ export function Category({
|
|||
<h2 className={styles.title}>{title}</h2>
|
||||
|
||||
<Sounds functional={functional} id={id} sounds={sounds} />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue