refactor: factorisation peek panel — NotePeekPanel réutilisable
Architecture: - components/note-peek/use-note-peek.ts: hook (fetch + state + events) - components/note-peek/note-peek-content.tsx: rendu markdown/richtext/KaTeX - components/note-peek/note-peek-panel.tsx: panel (overlay + inline modes) - components/note-peek/index.ts: exports - lib/use-scroll-to-block.ts: utilitaire scroll vers data-id insights/page.tsx: - ~95 lignes (state + fetch + KaTeX useEffect + AnimatePresence) → 10 lignes - peek.open(noteId) remplace handleNoteClick complexe - <NotePeekPanel mode=overlay /> remplace tout le JSX du panel NotePeekPanel gère: - markdown (MarkdownContent) + richtext (DOMPurify + KaTeX lazy) - RTL (slide gauche pour fa/ar) - prefers-reduced-motion - role=dialog + aria-modal (overlay mode) - loading spinner - bouton Maximize2 + X - renderContent prop pour custom (éditeur read-only)
This commit is contained in:
60
memento-note/components/note-peek/use-note-peek.ts
Normal file
60
memento-note/components/note-peek/use-note-peek.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { getNoteById } from '@/app/actions/notes'
|
||||
import type { Note } from '@/lib/types'
|
||||
import {
|
||||
NOTE_PEEK_OPEN_EVENT,
|
||||
NOTE_PEEK_CLOSE_EVENT,
|
||||
type NotePeekOpenDetail,
|
||||
} from '@/lib/note-peek-sync'
|
||||
|
||||
interface UseNotePeekOptions {
|
||||
selfNoteId?: string
|
||||
}
|
||||
|
||||
export function useNotePeek(opts: UseNotePeekOptions = {}) {
|
||||
const { selfNoteId } = opts
|
||||
const [note, setNote] = useState<Note | null>(null)
|
||||
const [blockId, setBlockId] = useState<string | undefined>(undefined)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const open = useCallback(async (targetNoteId: string, targetBlockId?: string) => {
|
||||
if (selfNoteId && targetNoteId === selfNoteId) return
|
||||
setLoading(true)
|
||||
setIsOpen(true)
|
||||
setBlockId(targetBlockId)
|
||||
try {
|
||||
const fetched = await getNoteById(targetNoteId)
|
||||
if (fetched) setNote(fetched)
|
||||
} catch {
|
||||
// silent
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [selfNoteId])
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
setNote(null)
|
||||
setBlockId(undefined)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const onOpen = (e: Event) => {
|
||||
const detail = (e as CustomEvent<NotePeekOpenDetail>).detail
|
||||
if (!detail) return
|
||||
void open(detail.noteId, detail.blockId)
|
||||
}
|
||||
const onClose = () => close()
|
||||
window.addEventListener(NOTE_PEEK_OPEN_EVENT, onOpen)
|
||||
window.addEventListener(NOTE_PEEK_CLOSE_EVENT, onClose)
|
||||
return () => {
|
||||
window.removeEventListener(NOTE_PEEK_OPEN_EVENT, onOpen)
|
||||
window.removeEventListener(NOTE_PEEK_CLOSE_EVENT, onClose)
|
||||
}
|
||||
}, [open, close])
|
||||
|
||||
return { note, blockId, loading, isOpen, open, close }
|
||||
}
|
||||
Reference in New Issue
Block a user