summaryrefslogtreecommitdiff
path: root/src/components/NotificationOverlay.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/NotificationOverlay.tsx')
-rw-r--r--src/components/NotificationOverlay.tsx117
1 files changed, 117 insertions, 0 deletions
diff --git a/src/components/NotificationOverlay.tsx b/src/components/NotificationOverlay.tsx
new file mode 100644
index 0000000..965f19d
--- /dev/null
+++ b/src/components/NotificationOverlay.tsx
@@ -0,0 +1,117 @@
+import { useEffect, useRef } from 'react';
+import { sendNotification } from '@tauri-apps/plugin-notification';
+import { useTaskStore } from '../store/taskStore';
+
+interface NotificationOverlayProps {
+ visible: boolean;
+ taskId: string | null;
+ onDismiss: () => void;
+}
+
+export function NotificationOverlay({ visible, taskId, onDismiss }: NotificationOverlayProps) {
+ const tasks = useTaskStore((s) => s.tasks);
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+ const task = tasks.find((t) => t.id === taskId) ?? null;
+
+ useEffect(() => {
+ if (!visible) return;
+
+ // OS notification
+ const body = task ? `"${task.name}" session complete.` : 'Session complete. Time for a break.';
+ try {
+ sendNotification({ title: 'Solstice', body });
+ } catch {
+ // Notifications may not be permitted in dev; ignore errors
+ }
+
+ // Auto-dismiss after 4 seconds
+ timerRef.current = setTimeout(() => {
+ onDismiss();
+ }, 4000);
+
+ return () => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+ };
+ }, [visible, task, onDismiss]);
+
+ if (!visible) return null;
+
+ return (
+ <div
+ onClick={onDismiss}
+ style={{
+ position: 'fixed',
+ inset: 0,
+ display: 'flex',
+ alignItems: 'flex-start',
+ justifyContent: 'center',
+ paddingTop: '40px',
+ zIndex: 200,
+ pointerEvents: 'auto',
+ }}
+ >
+ <div
+ onClick={(e) => e.stopPropagation()}
+ style={{
+ background: 'var(--ink-2)',
+ border: '1px solid var(--line-2)',
+ borderRadius: 'var(--r-3)',
+ boxShadow: 'var(--shadow-3)',
+ padding: '24px 32px',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '8px',
+ animation: 'slideDown 0.2s ease',
+ minWidth: '280px',
+ }}
+ >
+ <span
+ style={{
+ fontFamily: 'var(--font-display)',
+ fontStyle: 'italic',
+ fontSize: '28px',
+ fontWeight: 400,
+ color: 'var(--fg-1)',
+ lineHeight: 1.1,
+ }}
+ >
+ Session complete
+ </span>
+ {task && (
+ <span
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '13px',
+ color: 'var(--fg-3)',
+ }}
+ >
+ {task.name}
+ </span>
+ )}
+ <span
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '11px',
+ color: 'var(--fg-4)',
+ marginTop: '4px',
+ textTransform: 'uppercase',
+ letterSpacing: '0.1em',
+ }}
+ >
+ Click to dismiss
+ </span>
+ </div>
+
+ <style>{`
+ @keyframes slideDown {
+ from { opacity: 0; transform: translateY(-16px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ `}</style>
+ </div>
+ );
+}