React components, hooks, and utilities for building Papyr knowledge experiences
npm install papyr-reactPapyr exposes two layers, similar to shadcn/ui:
- Components – focused building blocks such as FileHierarchy, FileSearch, NoteViewer, and TableOfContents.
- Blocks – opinionated compositions that wire multiple components together. The new WorkspaceBlock uses DoubleSidebarLayout to render a file hierarchy + main + outline surface with independent scrolling.
``tsx
import {
WorkspaceBlock,
hydrateSearchIndex,
type WorkspaceBlockProps
} from 'papyr-react'
const searchIndex = hydrateSearchIndex(serializedIndex)
const workspaceProps: WorkspaceBlockProps = {
notes,
tree,
searchIndex,
graph,
initialSlug: notes[0]?.slug
}
export function App() {
return
}
`
Blocks accept slots so you can swap any pane:
`tsx`
rightSidebar={({ activeNote }) => (
)}
/>
Use components directly when you need finer control, or drop in a block for a ready-made layout.
When you load JSON produced by papyr-core, hydrate the search index beforePapyrProvider
passing it to :
`ts
import { hydrateSearchIndex } from 'papyr-react'
import { type SerializedSearchIndex } from 'papyr-core/runtime'
const serialized: SerializedSearchIndex = / build output /
const searchIndex = hydrateSearchIndex(serialized)
`
This accepts either the serialized payload or an already hydrated index, so it
can be used safely in shared code paths.
Use useRoutableActiveNote to keep Papyr state in sync with your routing
library. Provide two callbacks: one to read the current slug from the router and
another to push updates when the user selects a different note.
`tsx
import { useCallback } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useRoutableActiveNote } from 'papyr-react'
const navigate = useNavigate()
const { slug } = useParams<{ slug?: string }>()
const { activeNote, activeSlug, setActiveSlug } = useRoutableActiveNote(notes, {
getCurrentSlug: useCallback(() => slug ?? null, [slug]),
onSlugChange: useCallback(
(nextSlug) => {
navigate(nextSlug ? /notes/${nextSlug} : '/')`
},
[navigate]
)
})
The hook exposes the same API as useActiveNote`, so existing components (e.g.
sidebar or note viewer) continue to work while the URL stays in sync.