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:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user