refactor(notes): split NoteEditor into NoteForm and NoteReadView
This commit is contained in:
1 parent
7325485ee3
commit
e13652a753
5 files changed
+195
-110
No files matched your search
+13
-110
@@ -1,82 +1,14 @@
|
||||
import { useState } from 'react'
|
||||
import type { NoteEditorProps, NoteFormProps } from '../interfaces/note-editor'
|
||||
import type { NoteEditorProps } from '../interfaces/note-editor'
|
||||
import { isTitleValid, normalizeTitle } from '../lib/noteValidation'
|
||||
import DeleteModal from './DeleteModal'
|
||||
|
||||
const TITLE_REQUIRED_MESSAGE = 'Le titre est obligatoire.'
|
||||
|
||||
function NoteForm({
|
||||
title,
|
||||
content,
|
||||
titleMissing,
|
||||
cancelLabel,
|
||||
onTitleChange,
|
||||
onContentChange,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}: NoteFormProps) {
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-4 rounded-2xl border border-gray-200 bg-white p-4 shadow-sm"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
onSubmit()
|
||||
}}
|
||||
>
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-sm font-medium text-gray-500">Titre</span>
|
||||
<input
|
||||
value={title}
|
||||
onChange={(event) => onTitleChange(event.target.value)}
|
||||
autoFocus
|
||||
aria-invalid={titleMissing}
|
||||
className={`w-full rounded-lg border px-3 py-3 text-base focus:outline-none focus:ring-2 ${
|
||||
titleMissing
|
||||
? 'border-red-500 focus:ring-red-500/30'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500/30'
|
||||
}`}
|
||||
placeholder="Sans titre"
|
||||
/>
|
||||
</label>
|
||||
{titleMissing && (
|
||||
<span role="alert" className="text-sm text-red-600">
|
||||
{TITLE_REQUIRED_MESSAGE}
|
||||
</span>
|
||||
)}
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-sm font-medium text-gray-500">Contenu</span>
|
||||
<textarea
|
||||
value={content}
|
||||
onChange={(event) => onContentChange(event.target.value)}
|
||||
className="w-full min-h-48 rounded-lg border border-gray-300 px-3 py-3 text-base focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
placeholder="Écrire ici…"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-lg border border-gray-300 px-5 py-3 text-gray-600 hover:bg-gray-50 active:bg-gray-100"
|
||||
>
|
||||
{cancelLabel}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-lg bg-blue-600 px-5 py-3 text-white hover:bg-blue-700 active:bg-blue-800"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
import NoteForm from './NoteForm'
|
||||
import NoteReadView from './NoteReadView'
|
||||
|
||||
function NoteEditor(props: NoteEditorProps) {
|
||||
const [editing, setEditing] = useState(props.mode === 'create')
|
||||
const [title, setTitle] = useState(props.mode === 'edit' ? props.note.title : '')
|
||||
const [content, setContent] = useState(props.mode === 'edit' ? props.note.content : '')
|
||||
const [titleMissing, setTitleMissing] = useState(false)
|
||||
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||
|
||||
const handleTitleChange = (value: string) => {
|
||||
setTitle(value)
|
||||
@@ -114,45 +46,16 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
|
||||
const { note } = props
|
||||
return (
|
||||
<article className="flex flex-col gap-5 rounded-2xl border border-gray-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<h2 className="text-xl font-semibold leading-snug">{note.title || 'Sans titre'}</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
aria-label="Supprimer la note"
|
||||
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full text-2xl leading-none text-red-600 hover:bg-red-50 hover:text-red-700 active:bg-red-100"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<p className="whitespace-pre-wrap text-base leading-relaxed">
|
||||
{note.content || <span className="text-gray-400 italic">Note vide</span>}
|
||||
</p>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTitle(props.note.title)
|
||||
setContent(props.note.content)
|
||||
setEditing(true)
|
||||
}}
|
||||
className="rounded-lg bg-blue-600 px-5 py-3 text-white hover:bg-blue-700 active:bg-blue-800"
|
||||
>
|
||||
Éditer
|
||||
</button>
|
||||
</div>
|
||||
<DeleteModal
|
||||
open={confirmDelete}
|
||||
title={note.title}
|
||||
onConfirm={() => {
|
||||
setConfirmDelete(false)
|
||||
props.onDelete(note.id)
|
||||
}}
|
||||
onCancel={() => setConfirmDelete(false)}
|
||||
/>
|
||||
</article>
|
||||
<NoteReadView
|
||||
note={note}
|
||||
onEdit={() => {
|
||||
setTitle(note.title)
|
||||
setContent(note.content)
|
||||
setEditing(true)
|
||||
}}
|
||||
onDelete={props.onDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default NoteEditor
|
||||
export default NoteEditor
|
||||
Reference in new issue
Block a user