trails/apps/planner/app/components/NotesPanel.tsx
Ullrich Schäfer b09d810301
feat(planner): restyle sidebar on tokens (waypoints, notes, days)
Migrate the whole sidebar surface onto the design system:

- SidebarTabs: token tab bar, sage active indicator.
- WaypointSidebar: token surfaces/text; sage number badges; overnight
  marker uses the Badge (stop tone) primitive; route summary + stats
  footer in Geist Mono; destructive delete signalled via bg-nogo.
- NotesPanel + DayBreakdown headers/rows on tokens.
- i18n the previously-hardcoded empty-state string (en + de).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:39:12 +02:00

105 lines
2.7 KiB
TypeScript

import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { EditorView, keymap, placeholder } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { defaultKeymap } from "@codemirror/commands";
import { yCollab, yUndoManagerKeymap } from "y-codemirror.next";
import * as Y from "yjs";
import type { YjsState } from "~/lib/use-yjs";
interface NotesPanelProps {
yjs: YjsState;
}
const theme = EditorView.theme({
"&": {
height: "100%",
fontSize: "13px",
},
".cm-scroller": {
overflow: "auto",
fontFamily: "inherit",
},
".cm-content": {
padding: "12px",
caretColor: "#1f2937",
},
".cm-line": {
lineHeight: "1.5",
},
"&.cm-focused": {
outline: "none",
},
".cm-ySelectionInfo": {
fontSize: "10px",
fontFamily: "system-ui, sans-serif",
padding: "1px 4px",
borderRadius: "3px",
opacity: "0.9",
fontWeight: "500",
position: "absolute",
top: "-1.2em",
left: "-1px",
whiteSpace: "nowrap",
},
});
export function NotesPanel({ yjs }: NotesPanelProps) {
const { t } = useTranslation("planner");
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// Set awareness user fields for cursor display
const localState = yjs.awareness.getLocalState() as Record<string, unknown> | null;
const user = localState?.user as { name: string; color: string } | undefined;
if (user) {
yjs.awareness.setLocalStateField("user", {
...user,
colorLight: user.color + "30",
});
}
// Dedicated undo manager for notes (separate from waypoints undo)
const notesUndoManager = new Y.UndoManager(yjs.notes);
const state = EditorState.create({
doc: yjs.notes.toString(),
extensions: [
keymap.of([...yUndoManagerKeymap, ...defaultKeymap]),
EditorView.lineWrapping,
placeholder(t("notes.placeholder")),
theme,
yCollab(yjs.notes, yjs.awareness, {
undoManager: notesUndoManager,
}),
],
});
const view = new EditorView({
state,
parent: containerRef.current,
});
viewRef.current = view;
return () => {
view.destroy();
notesUndoManager.destroy();
viewRef.current = null;
};
}, [yjs]);
return (
<div className="flex h-full flex-col">
<div className="border-b border-border px-4 py-3">
<h2 className="text-sm font-medium text-text-hi">
{t("sidebar.notes")}
</h2>
</div>
<div ref={containerRef} className="flex-1 overflow-hidden" />
</div>
);
}