Nasaq

Notes

A notes workspace with notebooks and tags, a list and a coloured board, search and sort, pinned and sealed notes, and an editor with a rich or Markdown body, backlinks, properties, word count, an autosave state, a context menu, an actions menu and keyboard shortcuts, driven by callbacks.

PreviewOpen ↗

Code

const [notes, setNotes] = useState<Note[]>(initial);const patch = (id: string, p: Partial<Note>) => setNotes((all) => all.map((n) => (n.id === id ? { ...n, ...p, updatedAt: Date.now() } : n)));<Notes  notes={notes}  notebooks={notebooks}  onUpdate={async (id, p) => patch(id, p)}  onCreate={async () => {    const id = crypto.randomUUID();    setNotes((all) => [{ id, title: "", body: "", createdAt: Date.now(), updatedAt: Date.now() }, ...all]);    return { id };  }}  onDelete={async (id) => setNotes((all) => all.filter((n) => n.id !== id))}  onDuplicate={async (copy) => setNotes((all) => [copy, ...all])}  shareUrl={(n) => `https://app.example.com/n/${n.id}`}/>

Editors · beta

Live examples and controls: Notes in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/notes.json

A notes app in three parts: a sidebar of notebooks and tags, a list (or a coloured board) of notes, and the editor. It stores nothing. You pass notes and notebooks, the component calls your callbacks, you update your state. Use the parts alone as NotesView and NoteEditor, or together as Notes.

When to use

  • A personal or team notes area with notebooks, tags, pinned notes and a body that can be rich text or Markdown.
  • Any screen that needs one note surface with the same actions in the list, the editor and the keyboard.

When not to use

Import

import { Notes, NotesView, NoteEditor, type Note, type Notebook } from "@fadymondy/nasaq/web";

Quick start

const [notes, setNotes] = useState<Note[]>(initial);
const patch = (id: string, p: Partial<Note>) => setNotes((all) => all.map((n) => (n.id === id ? { ...n, ...p, updatedAt: Date.now() } : n)));

<Notes
  notes={notes}
  notebooks={notebooks}
  onUpdate={async (id, p) => patch(id, p)}
  onCreate={async () => {
    const id = crypto.randomUUID();
    setNotes((all) => [{ id, title: "", body: "", createdAt: Date.now(), updatedAt: Date.now() }, ...all]);
    return { id };
  }}
  onDelete={async (id) => setNotes((all) => all.filter((n) => n.id !== id))}
  onDuplicate={async (copy) => setNotes((all) => [copy, ...all])}
  shareUrl={(n) => `https://app.example.com/n/${n.id}`}
/>

An action whose callback is missing is left out of the menus (no onDuplicate, no Duplicate).

Anatomy

  • NotesSidebar: All, Pinned, Sealed, Archive, notebooks as a tree, tags. Hidden on narrow containers, where a chip bar does the same job.
  • NotesView: search, New note, sort, list or board toggle, a "⋯" menu for the list (export, notebooks), then the notes in sections (Pinned, Today, Yesterday, and so on).
  • NoteEditor: header (save state, pin, share, "⋯"), title, properties (notebook, tags, colour), body, footer (words, reading time, links).
  • Dialogs for move, tags, colour, share, export, seal, remove seal and delete, shared by every surface.

API

Notes props: notes, notebooks, activeId / defaultActiveId / onActiveChange, onCreate (return { id } to open it), onUpdate(id, patch), onDuplicate(copy), onDelete(id), onSeal, onRemoveSeal, onUnlock, onNotebookCreate / onNotebookRename / onNotebookDelete, shareUrl, shareProps, onExport, newId, defaultScope, defaultSort, defaultView, loading, error, onRetry, autosaveDelay, labels, now.

Callbacks return a promise. Resolve { error: "message" } to show a failure; throwing shows a generic one.

Note: id, title, body, format (rich HTML, or markdown), notebookId, tags, color, pinned, archived, sealed, createdAt, updatedAt.

NotesView (also menu, scope, query, sort, view controlled props, showScopeBar) and NoteEditor (note, onBack, onOpenNote, onUnlock, menu) take the same callbacks. Pass one useNoteMenu() result as menu to share dialogs, as Notes does.

Pure helpers: filterNotes, sortNotes, groupNotes, scopeCounts, backlinksOf, exportNote, duplicateNote, applyMarkdownFormat, htmlToMarkdown, saveReducer, matchNoteShortcut.

Sealed notes

A sealed note keeps its title and tags visible and hides its body until it is unlocked for the session. Notes remembers which ids are open; onUnlock checks the password. The component never sees the ciphertext: your server must withhold the body of a sealed note until the password is verified, and search must not index it. Nothing in the browser is a security boundary.

Context menu

Every note in the list and board, and the editor header and properties, has a context menu (context-click, long-press, Shift+F10 or the Menu key) and a "⋯" button with the same actions: open, pin, colour, tags, move, duplicate, share, export, seal or lock, archive, delete. Inside the note text the browser keeps its own menu so spelling and paste work; use the Format menu and Ctrl+B, Ctrl+I, Ctrl+K in Markdown notes.

Keyboard

KeysAction
Ctrl+N or Alt+N (Cmd+N on Mac)New note
Mod+Shift+PPin or unpin
Mod+Shift+AArchive or restore
Mod+Shift+DDuplicate
Mod+Shift+LSeal, lock or remove the seal
Mod+Shift+FFocus search
Alt+MOpen the note actions menu
Shift+F10 or MenuContext menu on the focused note
DeleteDelete the focused note (asks first)
Up and DownMove between notes
Mod+SSave now

Shortcuts match the key position, so they work on an Arabic layout.

Autosave

Edits are saved after autosaveDelay (800 ms) of quiet, on blur, on Mod+S and when the note closes. The header shows Unsaved, Saving, Saved or a failure with a Save now button. A change made during a save saves again.

Accessibility

  • The list is real lists with buttons; the open note has aria-current.
  • The save state is a polite live region; errors use role="alert".
  • Every icon button has a label; the "⋯" buttons name the note.
  • Coloured notes never rely on colour alone: pinned and sealed notes show an icon with a label.

RTL & i18n

English and Arabic strings ship; pass labels to override any. Titles and snippets use dir="auto", numbers and dates follow the Nasaq numbering, and the sidebar, back arrow and menus mirror.

Styling & tokens

Colours are the --nq-tag-<hue> and -soft tokens. Layout uses container queries, so it adapts to the space it is given, not the window.

Do / Don't

  • Do return the new id from onCreate so the note opens.
  • Do withhold sealed bodies on the server.
  • Don't store the unlock password.
  • Don't rely on the seal as encryption in the browser.

RichTextEditor, Markdown, ContextMenu, TreeView, ShareAction, ExportAction.

Lab

Components/Editors/Notes and Pages/App/Notes (Default, Arabic, Mobile).

On this page