{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-sticky-notes",
  "title": "useStickyNotes",
  "author": "Stickies <https://stickies.live>",
  "description": "A board of notes: add, change, remove, stack and select them, saved to localStorage by default.",
  "registryDependencies": [
    "https://stickies.live/r/stickies-format.json"
  ],
  "files": [
    {
      "path": "registry/stickies/hooks/use-sticky-notes.ts",
      "content": "\"use client\"\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport {\n  changeNote,\n  createNote,\n  notesOf,\n  parseDocument,\n  type StickyNoteData,\n  toDocument,\n} from \"@/registry/stickies/lib/stickies-format\"\n\nexport interface UseStickyNotesOptions {\n  /** The notes to start with, and to go back to on `reset()`. */\n  defaultNotes?: StickyNoteData[]\n  /** Controlled notes. Pair with `onNotesChange`; nothing is saved to the browser. */\n  notes?: StickyNoteData[]\n  onNotesChange?: (notes: StickyNoteData[]) => void\n  /** Keep the board in `localStorage`, as a Stickies document. On by default when uncontrolled. */\n  persist?: boolean\n  /** Where in `localStorage`. Give each board on a site its own. */\n  storageKey?: string\n}\n\n/**\n * A board of notes: adding, changing, removing, stacking and selecting them, saved to the\n * browser unless told not to. The array's order is the stack: the last note is on top.\n */\nexport function useStickyNotes({\n  defaultNotes = [],\n  notes: controlled,\n  onNotesChange,\n  persist = true,\n  storageKey = \"stickies:board\",\n}: UseStickyNotesOptions = {}) {\n  const isControlled = controlled !== undefined\n  const saving = persist && !isControlled\n  const [own, setOwn] = useState<StickyNoteData[]>(defaultNotes)\n  const notes = isControlled ? controlled : own\n  /** The latest notes, so several changes in one event build on each other. */\n  const latest = useRef(notes)\n  latest.current = notes\n  /** False until the saved board, if any, has been read: render nothing before then. */\n  const [ready, setReady] = useState(!saving)\n  /** Whether the board differs from `defaultNotes`, as far as anyone has changed it. */\n  const [edited, setEdited] = useState(false)\n  const [selected, setSelected] = useState<string[]>([])\n\n  const defaults = useRef(defaultNotes)\n  defaults.current = defaultNotes\n\n  useEffect(() => {\n    if (!saving) return\n    try {\n      const raw = localStorage.getItem(storageKey)\n      const doc = raw ? parseDocument(raw) : null\n      if (doc) {\n        setOwn(notesOf(doc))\n        setEdited(true)\n      }\n    } catch {\n      // Storage blocked: start from the defaults.\n    }\n    setReady(true)\n  }, [saving, storageKey])\n\n  useEffect(() => {\n    if (!saving || !ready || !edited) return\n    try {\n      localStorage.setItem(storageKey, JSON.stringify(toDocument(notes)))\n    } catch {\n      // Private mode or full storage: the board just isn't kept.\n    }\n  }, [notes, saving, ready, edited, storageKey])\n\n  const commit = useCallback(\n    (change: (notes: StickyNoteData[]) => StickyNoteData[]) => {\n      const next = change(latest.current)\n      if (next === latest.current) return\n      latest.current = next\n      if (!isControlled) setOwn(next)\n      setEdited(true)\n      onNotesChange?.(next)\n    },\n    [isControlled, onNotesChange],\n  )\n\n  const add = useCallback(\n    (fields: Partial<StickyNoteData> = {}) => {\n      const note = createNote(fields)\n      commit((all) => [...all, note])\n      return note\n    },\n    [commit],\n  )\n\n  /** Adds notes made elsewhere (pasted, say) on top, and selects them. */\n  const insert = useCallback(\n    (added: StickyNoteData[]) => {\n      if (added.length === 0) return\n      commit((all) => [...all, ...added])\n      setSelected(added.map((n) => n.id))\n    },\n    [commit],\n  )\n\n  /** Changes a note. Geometry alone (`touch: false`) leaves `updatedAt` as it was. */\n  const update = useCallback(\n    (id: string, fields: Partial<StickyNoteData>, { touch = true }: { touch?: boolean } = {}) =>\n      commit((all) => all.map((n) => (n.id === id ? (touch ? changeNote(n, fields) : { ...n, ...fields }) : n))),\n    [commit],\n  )\n\n  const remove = useCallback(\n    (ids: string | string[]) => {\n      const gone = new Set(Array.isArray(ids) ? ids : [ids])\n      commit((all) => (all.some((n) => gone.has(n.id)) ? all.filter((n) => !gone.has(n.id)) : all))\n      setSelected((s) => s.filter((id) => !gone.has(id)))\n    },\n    [commit],\n  )\n\n  /** Brings a note to the top of the stack. */\n  const raise = useCallback(\n    (id: string) =>\n      commit((all) => {\n        const at = all.findIndex((n) => n.id === id)\n        if (at < 0 || at === all.length - 1) return all\n        return [...all.slice(0, at), ...all.slice(at + 1), all[at]]\n      }),\n    [commit],\n  )\n\n  /** Selects a note; with `additive`, adds it to the selection or takes it out. */\n  const select = useCallback((id: string, { additive = false }: { additive?: boolean } = {}) => {\n    setSelected((s) => (additive ? (s.includes(id) ? s.filter((x) => x !== id) : [...s, id]) : s.length === 1 && s[0] === id ? s : [id]))\n  }, [])\n\n  const clearSelection = useCallback(() => setSelected((s) => (s.length ? [] : s)), [])\n\n  /** Back to `defaultNotes`, forgetting the saved board. */\n  const reset = useCallback(() => {\n    if (saving) {\n      try {\n        localStorage.removeItem(storageKey)\n      } catch {\n        // Nothing to forget.\n      }\n    }\n    latest.current = defaults.current\n    if (!isControlled) setOwn(defaults.current)\n    onNotesChange?.(defaults.current)\n    setEdited(false)\n    setSelected([])\n  }, [saving, storageKey, isControlled, onNotesChange])\n\n  const selectedNotes = useMemo(() => notes.filter((n) => selected.includes(n.id)), [notes, selected])\n\n  return {\n    notes,\n    ready,\n    edited,\n    add,\n    insert,\n    update,\n    remove,\n    raise,\n    selected,\n    selectedNotes,\n    select,\n    clearSelection,\n    setSelected,\n    reset,\n  }\n}\n\nexport type StickyNotesApi = ReturnType<typeof useStickyNotes>\n",
      "type": "registry:hook"
    }
  ],
  "docs": "See https://stickies.live/docs#use-sticky-notes for usage.",
  "categories": [
    "stickies"
  ],
  "type": "registry:hook"
}