feat(timeline): add pulse animation for in-flight drag mutations

Allocation bars that have active optimistic overrides (post-drag,
awaiting server confirmation) now pulse subtly via animate-pulse.
The pending set is derived from the existing optimisticAllocations
map keys, requiring no additional state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 13:28:46 +02:00
parent 7a5e98e2e9
commit 1df208dbcc
386 changed files with 657 additions and 81650 deletions
@@ -1,10 +1,11 @@
"use client";
import { useRef, useState } from "react";
import { useState } from "react";
import { AnimatedModal } from "~/components/ui/AnimatedModal.js";
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
import { DateInput } from "~/components/ui/DateInput.js";
import { useFocusTrap } from "~/hooks/useFocusTrap.js";
import { trpc } from "~/lib/trpc/client.js";
import { toDateInputValue } from "~/lib/format.js";
const RECURRENCE_OPTIONS = [
{ value: "", label: "None" },
@@ -26,15 +27,6 @@ interface ReminderModalProps {
onSuccess: () => void;
}
function toDateInputValue(date: Date | string | null | undefined): string {
if (!date) return "";
const d = typeof date === "string" ? new Date(date) : date;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function toTimeInputValue(date: Date | string | null | undefined): string {
if (!date) return "09:00";
const d = typeof date === "string" ? new Date(date) : date;
@@ -53,9 +45,6 @@ export function ReminderModal({ reminder, onClose, onSuccess }: ReminderModalPro
const [serverError, setServerError] = useState<string | null>(null);
const [confirmDelete, setConfirmDelete] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);
useFocusTrap(panelRef, true);
const utils = trpc.useUtils();
const createMutation = trpc.notification.createReminder.useMutation({
@@ -138,17 +127,8 @@ export function ReminderModal({ reminder, onClose, onSuccess }: ReminderModalPro
const labelClass = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
return (
<div
className="fixed inset-0 bg-black/50 z-50 flex items-start justify-center overflow-y-auto py-8"
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div
ref={panelRef}
className="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-lg mx-4"
onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
>
<>
<AnimatedModal open={true} onClose={onClose} maxWidth="max-w-lg">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
@@ -303,7 +283,7 @@ export function ReminderModal({ reminder, onClose, onSuccess }: ReminderModalPro
</div>
</div>
</form>
</div>
</AnimatedModal>
{confirmDelete && reminder && (
<ConfirmDialog
@@ -318,6 +298,6 @@ export function ReminderModal({ reminder, onClose, onSuccess }: ReminderModalPro
onCancel={() => setConfirmDelete(false)}
/>
)}
</div>
</>
);
}