tahamajs/IE / Projects /front /src /components /notes /NotesPanel.tsx
tahamajs's picture
download
raw
5.51 kB
import { useState } from "react";
import { FileText, Plus, Save, Trash2 } from "lucide-react";
interface Note {
id: string;
title: string;
body: string;
}
interface NotesPanelProps {
notes?: Note[];
onSave?: (notes: Note[]) => void | Promise<void>;
}
const STORAGE_KEY = "ie-workspace-notes";
const initialNote: Note = { id: "note-1", title: "Research note", body: "" };
export function NotesPanel({ notes, onSave }: NotesPanelProps) {
const [items, setItems] = useState<Note[]>(() => {
if (notes?.length) return notes;
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
return JSON.parse(saved);
} catch {
// Ignore
}
}
return [initialNote];
});
const [activeId, setActiveId] = useState(() => {
return items[0]?.id || initialNote.id;
});
const active = items.find((note) => note.id === activeId) || items[0];
const updateActive = (patch: Partial<Note>) => {
setItems((current) => {
const next = current.map((note) => note.id === active.id ? { ...note, ...patch } : note);
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
onSave?.(next);
return next;
});
};
const addNote = () => {
const next: Note = { id: `note-${Date.now()}`, title: "Untitled note", body: "" };
setItems((current) => {
const updated = [...current, next];
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
onSave?.(updated);
return updated;
});
setActiveId(next.id);
};
const removeNote = (id: string) => {
setItems((current) => {
const next = current.filter((note) => note.id !== id);
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
onSave?.(next);
setActiveId(next[0]?.id || "");
return next;
});
};
return (
<section className="flex h-full min-h-[320px] bg-white/30 dark:bg-slate-950/20 backdrop-blur-md">
<aside className="w-48 border-r border-white/10 dark:border-white/5 p-3 flex flex-col gap-2">
<button
onClick={addNote}
className="flex w-full items-center justify-center gap-2 rounded-xl bg-teal-600 hover:bg-teal-700 active:scale-95 text-white py-2 text-xs font-bold transition duration-200 shadow-md shadow-teal-600/10"
>
<Plus className="h-4 w-4" /> New Note
</button>
<div className="flex-1 overflow-y-auto space-y-1.5 custom-scrollbar">
{items.map((note) => (
<button
key={note.id}
onClick={() => setActiveId(note.id)}
className={`w-full rounded-xl px-3.5 py-2.5 text-left text-xs font-bold transition-all duration-200 block border ${
note.id === activeId
? "bg-teal-600 border-teal-500 text-white shadow-md shadow-teal-600/15"
: "bg-white/40 dark:bg-slate-900/40 border-transparent hover:bg-white/60 dark:hover:bg-slate-800/60 text-slate-700 dark:text-slate-300"
}`}
>
<div className="truncate font-semibold text-sm">{note.title || "Untitled note"}</div>
<div className={`text-[10px] mt-1 truncate ${note.id === activeId ? 'text-teal-100/80' : 'text-slate-400 dark:text-slate-500'}`}>
{note.body ? note.body.substring(0, 30) : "No additional text"}
</div>
</button>
))}
</div>
</aside>
{active ? (
<div className="flex min-w-0 flex-1 flex-col">
<header className="flex items-center gap-3 border-b border-white/10 dark:border-white/5 px-4 py-3 bg-white/20 dark:bg-slate-900/20">
<FileText className="h-4.5 w-4.5 text-teal-500 shrink-0" />
<input
value={active.title}
onChange={(event) => updateActive({ title: event.target.value })}
className="min-w-0 flex-1 bg-transparent text-sm font-bold text-slate-850 dark:text-white outline-none"
placeholder="Note Title"
/>
<button
onClick={() => {
onSave?.(items);
}}
className="rounded-xl p-2 hover:bg-white/40 dark:hover:bg-slate-800/40 transition active:scale-95 text-slate-500 hover:text-slate-800 dark:hover:text-white"
title="Save notes"
>
<Save className="h-4 w-4" />
</button>
<button
onClick={() => removeNote(active.id)}
className="rounded-xl p-2 text-rose-500 hover:bg-rose-50 dark:hover:bg-rose-950/30 transition active:scale-95"
title="Delete note"
>
<Trash2 className="h-4 w-4" />
</button>
</header>
<textarea
value={active.body}
onChange={(event) => updateActive({ body: event.target.value })}
className="min-h-0 flex-1 resize-none bg-transparent p-5 text-xs text-slate-700 dark:text-slate-350 outline-none leading-relaxed"
placeholder="Start writing..."
/>
</div>
) : (
<div className="flex flex-1 flex-col items-center justify-center text-slate-400 p-8">
<div className="text-4xl mb-3">📓</div>
<p className="text-sm font-bold">No note selected</p>
<p className="text-xs text-slate-500 mt-1">Create a note to start capturing your ideas.</p>
</div>
)}
</section>
);
}
export default NotesPanel;

Xet Storage Details

Size:
5.51 kB
·
Xet hash:
b16404617fad08f972c97970c3e6bb0d6ef4c0bafb0b0df9d2c3b40f499480f6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.