Harmonise la liste des notes et l’éditeur (titres, dates, retour), rend la saisie rapide et les raccourcis de l’accueil utilisables sur téléphone, et conserve le panneau latéral repliable.
116 lines
4.7 KiB
TypeScript
116 lines
4.7 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef } from 'react'
|
|
import { motion } from 'framer-motion'
|
|
import { X, Maximize2, LayoutGrid } from 'lucide-react'
|
|
import type { Note } from '@/lib/types'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { NoteEditorProvider, useNoteEditorContext } from './note-editor-context'
|
|
import { NoteTitleBlock } from './note-title-block'
|
|
import { NoteContentArea } from './note-content-area'
|
|
import { dateTextDirection, formatNoteCalendarDate } from '@/lib/utils/format-localized-date'
|
|
|
|
interface NoteEditorSplitPeekProps {
|
|
note: Note
|
|
blockId?: string
|
|
onClose: () => void
|
|
onOpenFully: () => void
|
|
onBackToDashboard?: () => void
|
|
}
|
|
|
|
function PeekEditorBody({ blockId }: { blockId?: string }) {
|
|
const { note } = useNoteEditorContext()
|
|
const { t, language } = useLanguage()
|
|
const dateDir = dateTextDirection(language)
|
|
const scrollRootRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!blockId) return
|
|
const timer = window.setTimeout(() => {
|
|
const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(blockId) : blockId
|
|
const el = scrollRootRef.current?.querySelector(`[data-id="${escaped}"]`)
|
|
el?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
}, 450)
|
|
return () => window.clearTimeout(timer)
|
|
}, [blockId, note.id])
|
|
|
|
return (
|
|
<div ref={scrollRootRef} className="flex-1 min-h-0 overflow-y-auto">
|
|
<div className="max-w-2xl mx-auto w-full px-6 sm:px-8 py-10 space-y-8 pb-24">
|
|
<p
|
|
className="text-[10px] font-medium tracking-normal text-[var(--color-concrete)]"
|
|
dir={dateDir}
|
|
lang={language === 'fa' ? 'fa' : language === 'ar' ? 'ar' : undefined}
|
|
suppressHydrationWarning
|
|
>
|
|
{formatNoteCalendarDate(note.contentUpdatedAt, language)}
|
|
</p>
|
|
<NoteTitleBlock />
|
|
<div className="max-w-xl mx-auto w-full">
|
|
<NoteContentArea />
|
|
</div>
|
|
<p className="text-[11px] text-[var(--color-concrete)] italic">{t('notePeek.readOnlyHint')}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function NoteEditorSplitPeek({ note, blockId, onClose, onOpenFully, onBackToDashboard }: NoteEditorSplitPeekProps) {
|
|
const { t, language } = useLanguage()
|
|
const isRtl = language === 'fa' || language === 'ar'
|
|
|
|
return (
|
|
<motion.aside
|
|
initial={{ width: 0, opacity: 0 }}
|
|
animate={{ width: 'min(50vw, 720px)', opacity: 1 }}
|
|
exit={{ width: 0, opacity: 0 }}
|
|
transition={{ type: 'spring', stiffness: 340, damping: 34 }}
|
|
className={`shrink-0 h-full min-h-0 bg-[#fafaf9] dark:bg-zinc-950 flex flex-col overflow-hidden z-40 ${
|
|
isRtl
|
|
? 'border-r border-black/10 dark:border-white/10 shadow-[4px_0_24px_-12px_rgba(0,0,0,0.12)]'
|
|
: 'border-l border-black/10 dark:border-white/10 shadow-[-4px_0_24px_-12px_rgba(0,0,0,0.12)]'
|
|
}`}
|
|
aria-label={t('notePeek.panelLabel')}
|
|
>
|
|
<NoteEditorProvider note={note} readOnly fullPage>
|
|
<div className="shrink-0 px-4 py-2.5 flex items-center justify-between gap-3 border-b border-black/[0.06] dark:border-white/[0.06] bg-white/80 dark:bg-zinc-900/80 backdrop-blur-sm">
|
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-[var(--color-concrete)] truncate">
|
|
{t('notePeek.label')}
|
|
</span>
|
|
<div className="flex items-center gap-1 shrink-0">
|
|
{onBackToDashboard && (
|
|
<button
|
|
type="button"
|
|
onClick={onBackToDashboard}
|
|
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wide text-ink dark:text-dark-ink hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
|
>
|
|
<LayoutGrid size={12} />
|
|
{t('notes.backToHome')}
|
|
</button>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={onOpenFully}
|
|
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wide text-blue-600 dark:text-blue-400 hover:bg-blue-500/10 transition-colors"
|
|
title={t('notePeek.openFullyHelp')}
|
|
>
|
|
<Maximize2 size={12} />
|
|
{t('notePeek.openFully')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="p-1.5 rounded-lg text-[var(--color-concrete)] hover:text-[var(--color-ink)] hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
|
title={t('notePeek.close')}
|
|
aria-label={t('notePeek.close')}
|
|
>
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<PeekEditorBody blockId={blockId} />
|
|
</NoteEditorProvider>
|
|
</motion.aside>
|
|
)
|
|
}
|