Files
Momento/memento-note/components/note-editor/note-editor-split-peek.tsx
Antigravity 3bdbec575a feat(ui): panneau repliable, carnets plus lisibles et accueil plus clair
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.
2026-09-05 20:53:48 +00:00

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>
)
}