Replace snooze button group with clock icon dropdown
Single clock icon opens a dropdown with 5/10/15 min options instead of three inline buttons. Shared SnoozeDropdown component used in both AlertBanner and toast notifications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b7251b72c7
commit
89b4bd4870
@ -1,5 +1,6 @@
|
|||||||
import { Bell, X } from 'lucide-react';
|
import { Bell, X } from 'lucide-react';
|
||||||
import { getRelativeTime } from '@/lib/date-utils';
|
import { getRelativeTime } from '@/lib/date-utils';
|
||||||
|
import SnoozeDropdown from '@/components/reminders/SnoozeDropdown';
|
||||||
import type { Reminder } from '@/types';
|
import type { Reminder } from '@/types';
|
||||||
|
|
||||||
interface AlertBannerProps {
|
interface AlertBannerProps {
|
||||||
@ -33,18 +34,7 @@ export default function AlertBanner({ alerts, onDismiss, onSnooze }: AlertBanner
|
|||||||
<span className="text-[11px] text-muted-foreground shrink-0 whitespace-nowrap">
|
<span className="text-[11px] text-muted-foreground shrink-0 whitespace-nowrap">
|
||||||
{alert.remind_at ? getRelativeTime(alert.remind_at) : ''}
|
{alert.remind_at ? getRelativeTime(alert.remind_at) : ''}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-0.5 text-[11px] shrink-0">
|
<SnoozeDropdown onSnooze={(m) => onSnooze(alert.id, m)} label={alert.title} />
|
||||||
{([5, 10, 15] as const).map((m) => (
|
|
||||||
<button
|
|
||||||
key={m}
|
|
||||||
onClick={() => onSnooze(alert.id, m)}
|
|
||||||
aria-label={`Snooze "${alert.title}" for ${m} minutes`}
|
|
||||||
className="px-1.5 py-0.5 rounded bg-secondary hover:bg-accent/10 hover:text-accent text-muted-foreground transition-colors"
|
|
||||||
>
|
|
||||||
{m}m
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => onDismiss(alert.id)}
|
onClick={() => onDismiss(alert.id)}
|
||||||
aria-label={`Dismiss "${alert.title}"`}
|
aria-label={`Dismiss "${alert.title}"`}
|
||||||
|
|||||||
54
frontend/src/components/reminders/SnoozeDropdown.tsx
Normal file
54
frontend/src/components/reminders/SnoozeDropdown.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { Clock } from 'lucide-react';
|
||||||
|
|
||||||
|
interface SnoozeDropdownProps {
|
||||||
|
onSnooze: (minutes: 5 | 10 | 15) => void;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OPTIONS: { value: 5 | 10 | 15; label: string }[] = [
|
||||||
|
{ value: 5, label: '5 minutes' },
|
||||||
|
{ value: 10, label: '10 minutes' },
|
||||||
|
{ value: 15, label: '15 minutes' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function SnoozeDropdown({ onSnooze, label }: SnoozeDropdownProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
function handleClick(e: MouseEvent) {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', handleClick);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClick);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" ref={ref}>
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
aria-label={`Snooze "${label}"`}
|
||||||
|
className="p-1 rounded hover:bg-accent/10 hover:text-accent text-muted-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<Clock className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="absolute right-0 bottom-full mb-1 w-32 rounded-md border bg-popover shadow-lg z-50 py-1 animate-fade-in">
|
||||||
|
{OPTIONS.map((opt) => (
|
||||||
|
<button
|
||||||
|
key={opt.value}
|
||||||
|
onClick={() => { onSnooze(opt.value); setOpen(false); }}
|
||||||
|
className="flex items-center w-full px-3 py-1.5 text-xs hover:bg-card-elevated transition-colors text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { toast } from 'sonner';
|
|||||||
import { Bell } from 'lucide-react';
|
import { Bell } from 'lucide-react';
|
||||||
import api from '@/lib/api';
|
import api from '@/lib/api';
|
||||||
import { getRelativeTime } from '@/lib/date-utils';
|
import { getRelativeTime } from '@/lib/date-utils';
|
||||||
|
import SnoozeDropdown from '@/components/reminders/SnoozeDropdown';
|
||||||
import type { Reminder } from '@/types';
|
import type { Reminder } from '@/types';
|
||||||
|
|
||||||
const MAX_TOASTS = 3;
|
const MAX_TOASTS = 3;
|
||||||
@ -102,19 +103,11 @@ export function AlertsProvider({ children }: { children: ReactNode }) {
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-foreground truncate">{reminder.title}</p>
|
<p className="text-sm font-medium text-foreground truncate">{reminder.title}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{timeAgo}</p>
|
<p className="text-xs text-muted-foreground mt-0.5">{timeAgo}</p>
|
||||||
<div className="flex items-center gap-1.5 mt-2">
|
<div className="flex items-center gap-1 mt-2">
|
||||||
<div className="flex items-center gap-0.5 text-[11px]">
|
<SnoozeDropdown
|
||||||
{[5, 10, 15].map((m) => (
|
onSnooze={(m) => snoozeRef.current(reminder.id, m)}
|
||||||
<button
|
label={reminder.title}
|
||||||
key={m}
|
/>
|
||||||
onClick={() => snoozeRef.current(reminder.id, m as 5 | 10 | 15)}
|
|
||||||
aria-label={`Snooze "${reminder.title}" for ${m} minutes`}
|
|
||||||
className="px-1.5 py-0.5 rounded bg-secondary hover:bg-card-elevated text-muted-foreground hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
{m}m
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => dismissRef.current(reminder.id)}
|
onClick={() => dismissRef.current(reminder.id)}
|
||||||
aria-label={`Dismiss "${reminder.title}"`}
|
aria-label={`Dismiss "${reminder.title}"`}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user