import { useId, useRef } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' import remarkBreaks from 'remark-breaks' import { DAY_NOTE_DETAIL_MAX, NOTE_ICONS, dayNoteNearLimit, getNoteIcon } from './DayPlanSidebar.constants' import NoteFormatToolbar from '../shared/NoteFormatToolbar' import NoteColorPicker from '../shared/NoteColorPicker' import { noteSurface } from './noteSurface' import { markdownLinkComponents } from '../shared/markdownLink' import { Tooltip } from '../shared/Tooltip' import { DeleteButton, DialogButton, DialogFooter, DialogHeader, DialogShell, DialogTile, FooterSpacer, NEUTRAL_TINT, fs } from '../shared/DialogShell' import { EditorField, TEXTAREA } from '../shared/dialogParts' import { EYEBROW } from './bookings/bookingParts' import { tintOf } from './planParts' interface NoteModalUi { mode: 'add' | 'edit' noteId?: number icon: string text: string time: string color?: string | null } type NoteUiMap = Record interface DayPlanSidebarNoteModalProps { noteUi: NoteUiMap setNoteUi: (updater: (prev: NoteUiMap) => NoteUiMap) => void /** * The hook's handle on the title field. The field sits in the dialog's head * band now and takes the focus itself when the dialog opens, so the hook's * delayed focus call finds nothing to do. */ noteInputRef: React.RefObject cancelNote: (dayId: number) => void saveNote: (dayId: number) => void /** Asks the planner to confirm and then delete the note being edited (#2249). */ onRequestDelete: (dayId: number, noteId: number) => void t: (key: string, params?: Record) => string } type NoteDialogProps = Omit & { dayId: string; ui: NoteModalUi | undefined } /** * Add/edit dialog for a day note (#1629), one per day whose note is open. * * The title is typed into the head band, whose tile already shows the icon in * the note's colour. Below it two columns on anything wider than a phone: how * the note looks on the left (icon, colour, and a preview of the card it will * become), what it says on the right. The preview is the reason the colour * picker is worth having: a swatch row tells you nothing about what a tinted * card looks like next to a place. */ export function DayPlanSidebarNoteModal({ noteUi, setNoteUi, cancelNote, saveNote, onRequestDelete, t }: DayPlanSidebarNoteModalProps) { return ( <> {Object.entries(noteUi).map(([dayId, ui]) => ( ))} ) } function NoteDialog({ dayId, ui, ...rest }: NoteDialogProps) { return ui ? : null } function OpenNoteDialog({ dayId, ui, setNoteUi, cancelNote, saveNote, onRequestDelete, t }: NoteDialogProps & { ui: NoteModalUi }) { const bodyRef = useRef(null) const labelId = useId() const bodyId = `${labelId}-body` const day = Number(dayId) const patch = (fields: Partial) => setNoteUi(prev => ({ ...prev, [dayId]: { ...(prev[dayId] as NoteModalUi), ...fields } })) const color = ui.color ?? null const surface = noteSurface(color) const NoteIcon = getNoteIcon(ui.icon) const canSave = Boolean(ui.text?.trim()) const bodyLen = ui.time?.length || 0 const header = ( } tint={color ? tintOf(color, 12) : NEUTRAL_TINT} labelId={labelId} onClose={() => cancelNote(day)} eyebrow={ui.mode === 'add' ? t('dayplan.noteAdd') : t('dayplan.noteEdit')} titleInput={{ value: ui.text, onChange: text => patch({ text }), label: t('dayplan.noteTitle'), placeholder: t('dayplan.noteTitle'), autoFocus: true, required: true, onKeyDown: e => { if (e.key === 'Enter') saveNote(day) }, }} /> ) // Delete lives here rather than on the row: the row's floating pencil/trash // pill covered the reorder chevrons and, on a coarse pointer, never went // away (#2249). The planner still confirms before it deletes. const footer = ( {ui.mode === 'edit' && ui.noteId != null && onRequestDelete(day, ui.noteId!)} />} cancelNote(day)}>{t('common.cancel')} saveNote(day)} disabled={!canSave}> {ui.mode === 'add' ? t('common.add') : t('common.save')} ) return ( cancelNote(day)} labelledBy={labelId} width="editor" header={header} footer={footer}>
{/* Left: how it looks. */}
{NOTE_ICONS.map(({ id, Icon }) => { const on = ui.icon === id // The icons have no names of their own in the catalogue, so the id is what the tooltip says. return ( ) })}
patch({ color: c })} /> {/* The same shape the card takes in the day plan. */}
{ui.text?.trim() || t('dayplan.noteTitle')}
{/* Right: what it says. */}
patch({ time: v })} compact />