mirror of
https://github.com/remvze/moodist.git
synced 2026-03-11 14:53:51 +08:00
feat: add basic animations with Framer Motion
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user