mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 00:44:14 +00:00
feat: add move up and down functionality
This commit is contained in:
parent
d356d77aa9
commit
3e11fb6123
10 changed files with 178 additions and 13 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/react": "3.6.0",
|
||||
"@floating-ui/react": "0.26.0",
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"@radix-ui/react-dropdown-menu": "2.0.6",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@types/howler": "2.2.10",
|
||||
|
|
@ -3598,6 +3599,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
|
||||
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
|
||||
},
|
||||
"node_modules/@formkit/auto-animate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.2.tgz",
|
||||
"integrity": "sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.11",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/react": "3.6.0",
|
||||
"@floating-ui/react": "0.26.0",
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"@radix-ui/react-dropdown-menu": "2.0.6",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@types/howler": "2.2.10",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
|
||||
import { Modal } from '@/components/modal';
|
||||
|
||||
import { Form } from './form';
|
||||
|
|
@ -11,11 +13,13 @@ interface TimerProps {
|
|||
}
|
||||
|
||||
export function CountdownTimer({ onClose, show }: TimerProps) {
|
||||
const [containerRef, enableAnimations] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
<Modal persist show={show} onClose={onClose}>
|
||||
<h2 className={styles.title}>Countdown Timer</h2>
|
||||
<Form />
|
||||
<Timers />
|
||||
<Form enableAnimations={enableAnimations} />
|
||||
<Timers enableAnimations={enableAnimations} ref={containerRef} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ import { waitUntil } from '@/helpers/wait';
|
|||
|
||||
import styles from './form.module.css';
|
||||
|
||||
export function Form() {
|
||||
interface FormProps {
|
||||
enableAnimations: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export function Form({ enableAnimations }: FormProps) {
|
||||
const [name, setName] = useState('');
|
||||
const [hours, setHours] = useState(0);
|
||||
const [minutes, setMinutes] = useState(10);
|
||||
|
|
@ -25,6 +29,8 @@ export function Form() {
|
|||
|
||||
if (totalSeconds === 0) return;
|
||||
|
||||
enableAnimations(false);
|
||||
|
||||
const id = add({
|
||||
name,
|
||||
total: totalSeconds,
|
||||
|
|
@ -37,6 +43,8 @@ export function Form() {
|
|||
document
|
||||
.getElementById(`timer-${id}`)
|
||||
?.scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
enableAnimations(true);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from 'react-icons/io5/index';
|
||||
|
||||
import { ReverseTimer } from './reverse-timer';
|
||||
import { Toolbar } from './toolbar';
|
||||
|
||||
import { useCountdownTimers } from '@/stores/countdown-timers';
|
||||
import { useAlarm } from '@/hooks/use-alarm';
|
||||
|
|
@ -17,17 +18,18 @@ import { cn } from '@/helpers/styles';
|
|||
import styles from './timer.module.css';
|
||||
|
||||
interface TimerProps {
|
||||
enableAnimations: (enabled: boolean) => void;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function Timer({ id }: TimerProps) {
|
||||
export function Timer({ enableAnimations, id }: TimerProps) {
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const lastActiveTimeRef = useRef<number | null>(null);
|
||||
const lastStateRef = useRef<{ spent: number; total: number } | null>(null);
|
||||
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const { name, spent, total } = useCountdownTimers(state =>
|
||||
const { first, last, name, spent, total } = useCountdownTimers(state =>
|
||||
state.getTimer(id),
|
||||
) || { name: '', spent: 0, total: 0 };
|
||||
|
||||
|
|
@ -75,10 +77,14 @@ export function Timer({ id }: TimerProps) {
|
|||
const handleDelete = () => {
|
||||
if (isRunning) return showSnackbar('Please first stop the timer.');
|
||||
|
||||
enableAnimations(false);
|
||||
|
||||
setIsDeleting(true);
|
||||
setSnapshot({ spent, total });
|
||||
|
||||
deleteTimer(id);
|
||||
|
||||
setTimeout(() => enableAnimations(true), 100);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -149,6 +155,8 @@ export function Timer({ id }: TimerProps) {
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<Toolbar first={first} id={id} last={last} />
|
||||
|
||||
<ReverseTimer spent={spent} />
|
||||
|
||||
<div className={styles.left}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { Toolbar } from './toolbar';
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
.toolbar {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 4px;
|
||||
background-color: var(--color-neutral-50);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 4px;
|
||||
|
||||
& button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground-subtle);
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
transition: 0.2s;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-neutral-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io/index';
|
||||
|
||||
import { useCountdownTimers } from '@/stores/countdown-timers';
|
||||
|
||||
import styles from './toolbar.module.css';
|
||||
|
||||
interface ToolbarProps {
|
||||
first: boolean;
|
||||
id: string;
|
||||
last: boolean;
|
||||
}
|
||||
|
||||
export function Toolbar({ first, id, last }: ToolbarProps) {
|
||||
const moveUp = useCountdownTimers(state => state.moveUp);
|
||||
const moveDown = useCountdownTimers(state => state.moveDown);
|
||||
|
||||
return (
|
||||
<div className={styles.toolbar}>
|
||||
<button
|
||||
disabled={first}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
moveUp(id);
|
||||
}}
|
||||
>
|
||||
<IoIosArrowUp />
|
||||
</button>
|
||||
<button
|
||||
disabled={last}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
moveDown(id);
|
||||
}}
|
||||
>
|
||||
<IoIosArrowDown />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useMemo, forwardRef } from 'react';
|
||||
|
||||
import { Timer } from './timer';
|
||||
import { Notice } from './notice';
|
||||
|
|
@ -7,7 +7,14 @@ import { useCountdownTimers } from '@/stores/countdown-timers';
|
|||
|
||||
import styles from './timers.module.css';
|
||||
|
||||
export function Timers() {
|
||||
interface TimersProps {
|
||||
enableAnimations: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export const Timers = forwardRef(function Timers(
|
||||
{ enableAnimations }: TimersProps,
|
||||
ref: React.ForwardedRef<HTMLDivElement>,
|
||||
) {
|
||||
const timers = useCountdownTimers(state => state.timers);
|
||||
const spent = useCountdownTimers(state => state.spent());
|
||||
const total = useCountdownTimers(state => state.total());
|
||||
|
|
@ -30,13 +37,19 @@ export function Timers() {
|
|||
)}
|
||||
</header>
|
||||
|
||||
{timers.map(timer => (
|
||||
<Timer id={timer.id} key={timer.id} />
|
||||
))}
|
||||
<div ref={ref}>
|
||||
{timers.map(timer => (
|
||||
<Timer
|
||||
enableAnimations={enableAnimations}
|
||||
id={timer.id}
|
||||
key={timer.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Notice />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ interface State {
|
|||
interface Actions {
|
||||
add: (timer: { name: string; total: number }) => string;
|
||||
delete: (id: string) => void;
|
||||
getTimer: (id: string) => Timer;
|
||||
getTimer: (id: string) => Timer & { first: boolean; last: boolean };
|
||||
moveDown: (id: string) => void;
|
||||
moveUp: (id: string) => void;
|
||||
rename: (id: string, newName: string) => void;
|
||||
reset: (id: string) => void;
|
||||
tick: (id: string, amount?: number) => void;
|
||||
|
|
@ -52,7 +54,53 @@ export const useCountdownTimers = create<State & Actions>()(
|
|||
},
|
||||
|
||||
getTimer(id) {
|
||||
return get().timers.filter(timer => timer.id === id)[0];
|
||||
const timers = get().timers;
|
||||
const timer = timers.filter(timer => timer.id === id)[0];
|
||||
const index = timers.indexOf(timer);
|
||||
|
||||
return {
|
||||
...timer,
|
||||
first: index === 0,
|
||||
last: index === timers.length - 1,
|
||||
};
|
||||
},
|
||||
|
||||
moveDown(id) {
|
||||
set(state => {
|
||||
const index = state.timers.findIndex(timer => timer.id === id);
|
||||
|
||||
if (index < state.timers.length - 1) {
|
||||
const newTimers = [...state.timers];
|
||||
|
||||
[newTimers[index + 1], newTimers[index]] = [
|
||||
newTimers[index],
|
||||
newTimers[index + 1],
|
||||
];
|
||||
|
||||
return { timers: newTimers };
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
},
|
||||
|
||||
moveUp(id) {
|
||||
set(state => {
|
||||
const index = state.timers.findIndex(timer => timer.id === id);
|
||||
|
||||
if (index > 0) {
|
||||
const newTimers = [...state.timers];
|
||||
|
||||
[newTimers[index - 1], newTimers[index]] = [
|
||||
newTimers[index],
|
||||
newTimers[index - 1],
|
||||
];
|
||||
|
||||
return { timers: newTimers };
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
},
|
||||
|
||||
rename(id, newName) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue