From 30bcb17f760ba7d5e0e044747cf7af1afe62cc24 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Fri, 22 May 2026 11:16:09 +0530 Subject: [PATCH 1/3] improve quick note editor interactions --- apps/web/components/quick-note-card.tsx | 395 +++++++++++++++++----- apps/web/components/text-editor/index.tsx | 12 + 2 files changed, 329 insertions(+), 78 deletions(-) diff --git a/apps/web/components/quick-note-card.tsx b/apps/web/components/quick-note-card.tsx index bb7277341..31dbf61ae 100644 --- a/apps/web/components/quick-note-card.tsx +++ b/apps/web/components/quick-note-card.tsx @@ -1,11 +1,14 @@ "use client" -import { useRef, useCallback } from "react" +import { useRef, useCallback, useEffect, useMemo, useState } from "react" +import { createPortal } from "react-dom" +import { AnimatePresence, motion } from "motion/react" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" -import { Maximize2, Plus, Loader2 } from "lucide-react" +import { Maximize2, Plus, Loader2, X } from "lucide-react" import { useProject } from "@/stores" import { useQuickNoteDraft } from "@/stores/quick-note-draft" +import { TextEditor } from "./text-editor" interface QuickNoteCardProps { onSave: (content: string) => void @@ -13,24 +16,56 @@ interface QuickNoteCardProps { isSaving?: boolean } +type NoteRect = { + left: number + top: number + width: number + height: number +} + +function getExpandedRect(source: NoteRect): NoteRect { + const viewportWidth = window.innerWidth + const viewportHeight = window.innerHeight + const margin = viewportWidth < 768 ? 16 : 32 + const availableWidth = viewportWidth - source.left - margin + const availableHeight = viewportHeight - source.top - margin + const width = Math.min(1008, Math.max(source.width, availableWidth)) + const height = Math.min(720, Math.max(source.height, availableHeight)) + + return { + left: source.left, + top: source.top, + width, + height, + } +} + export function QuickNoteCard({ onSave, onMaximize, isSaving = false, }: QuickNoteCardProps) { - const textareaRef = useRef(null) + const cardRef = useRef(null) + const wasSavingRef = useRef(isSaving) const { selectedProject } = useProject() const { draft, setDraft } = useQuickNoteDraft(selectedProject) + const [isExpanded, setIsExpanded] = useState(false) + const [sourceRect, setSourceRect] = useState(null) + const [targetRect, setTargetRect] = useState(null) + const [expandedInitialContent, setExpandedInitialContent] = useState< + string | undefined + >(undefined) + const [isMounted, setIsMounted] = useState(false) const handleChange = useCallback( - (e: React.ChangeEvent) => { - setDraft(e.target.value) + (content: string) => { + setDraft(content) }, [setDraft], ) const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { + (e: React.KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault() if (draft.trim() && !isSaving) { @@ -41,6 +76,27 @@ export function QuickNoteCard({ [draft, isSaving, onSave], ) + const handleExpand = useCallback(() => { + const rect = cardRef.current?.getBoundingClientRect() + if (!rect) return + + const nextSourceRect = { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + } + + setSourceRect(nextSourceRect) + setTargetRect(getExpandedRect(nextSourceRect)) + setExpandedInitialContent(draft || undefined) + setIsExpanded(true) + }, [draft]) + + const handleClose = useCallback(() => { + setIsExpanded(false) + }, []) + const handleSaveClick = useCallback(() => { if (draft.trim() && !isSaving) { onSave(draft) @@ -48,112 +104,295 @@ export function QuickNoteCard({ }, [draft, isSaving, onSave]) const handleMaximizeClick = useCallback(() => { + setIsExpanded(false) onMaximize(draft) }, [draft, onMaximize]) const canSave = draft.trim().length > 0 && !isSaving + const previewText = useMemo(() => { + const trimmed = draft.trim() + if (!trimmed) return null + return trimmed.replace(/\s+/g, " ") + }, [draft]) + + useEffect(() => { + setIsMounted(true) + }, []) + + useEffect(() => { + if (!isExpanded || !sourceRect) return + + const handleResize = () => setTargetRect(getExpandedRect(sourceRect)) + window.addEventListener("resize", handleResize) + return () => window.removeEventListener("resize", handleResize) + }, [isExpanded, sourceRect]) + + useEffect(() => { + if (!isExpanded) return + + const previousOverflow = document.body.style.overflow + document.body.style.overflow = "hidden" + + const handleGlobalKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault() + handleClose() + } + } + + document.addEventListener("keydown", handleGlobalKeyDown) + + return () => { + document.body.style.overflow = previousOverflow + document.removeEventListener("keydown", handleGlobalKeyDown) + } + }, [isExpanded, handleClose]) + + useEffect(() => { + if (wasSavingRef.current && !isSaving && draft.trim().length === 0) { + setIsExpanded(false) + } + wasSavingRef.current = isSaving + }, [draft, isSaving]) return ( -
+ <>
- - -