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