@document-explorer/react
v0.1.1
Published
Clean, configuration-driven document explorer for React. Bring your own data.
Maintainers
Readme
@document-explorer/react
Turn any document data into a clean, navigable explorer — in React.
npm install @document-explorer/reactimport { DocumentExplorer } from '@document-explorer/react';
import '@document-explorer/react/styles.css';
<DocumentExplorer data={documents} />;Bring your own data
<DocumentExplorer
data={apiResponse}
mapper={{
id: (d) => d.document_id,
name: (d) => d.document_title,
type: (d) => (d.kind === 'DIRECTORY' ? 'folder' : 'file'),
parentId: (d) => d.folder_id,
size: 'file_size',
metadata: 'extra',
}}
/>Props
| Prop | Type | Notes |
| --- | --- | --- |
| data | T[] | Flat rows or nested children. |
| mapper | DocumentMapper<T> | Omit when your data is already canonical. |
| title | ReactNode | Heading in the explorer header. |
| rootLabel | string | Label for the root breadcrumb. Default 'Home'. |
| view / defaultView / views | 'list' \| 'grid' \| 'tree' | views={[]} hides the switcher. |
| search | boolean \| { scope, searchableFields, matchMode, placeholder } | scope: 'global' searches the whole tree. |
| sort | { key, direction, foldersFirst } | key accepts 'metadata.owner'. |
| columns | (ColumnDef \| string)[] | Strings are shorthand; render gives full control of a cell. |
| columnAlign | 'left' \| 'center' \| 'right' | Alignment for every column. Default 'left'; a column's own align overrides it. |
| resizableColumns | boolean | Adds a drag handle to each column edge. Off by default. |
| columnWidths | Record<string, number> | Starting widths, e.g. a restored layout. |
| selection | boolean \| { mode, files, folders, max } | |
| actions | { view, download } \| ActionDef[] | |
| interaction | { folderClick, fileClick, doubleClick } | Defaults to open / select / open. |
| renderers | { file, folder, row, icon, empty, loading, error, toolbar } | |
| loading / error | boolean / unknown | Show the skeleton or error state. |
| locale | string | For size and date formatting. |
Events: onFolderOpen, onDocumentOpen, onSelectionChange, onSearch,
onSort, onViewChange, onView, onDownload, onColumnResize(key, width,
widths), and onAction(id, item) for custom actions.
Avatars
Showing a person in a column is built in:
import { DocumentExplorer, avatarColumn } from '@document-explorer/react';
<DocumentExplorer columns={['name', avatarColumn({ key: 'metadata.owner' }), 'size']} />display and image are independent: display decides how much to show,
image decides whether the avatar is a photo or initials. They compose:
avatarColumn({ key: 'metadata.owner' }) // initials + name
avatarColumn({ key: 'metadata.owner', display: 'avatar' }) // initials, name on hover
avatarColumn({ key: 'metadata.owner', display: 'name' }) // name only
avatarColumn({ key: 'metadata.owner', image: 'metadata.pic' }) // photo + name
avatarColumn({ key: 'metadata.owner', image: 'metadata.pic', display: 'avatar' })
// photo alone, name on hoverdisplay: 'avatar' moves the name to a tooltip and to screen-reader text,
so a narrow column stays usable. An image falls back to initials if it fails to
load. Each name gets a stable colour from six theme tokens, so the same person
looks the same everywhere.
Every ColumnDef field passes through, and name, image, tooltip, color
and fallback fine-tune the rest.
Custom cell content
For anything else — a status pill, a badge — render the cell yourself:
<DocumentExplorer
columns={[
'name',
{
key: 'metadata.status',
label: 'Status',
render: (item) => <StatusPill status={item.metadata?.status} />,
},
]}
/>Badges
import { badgeColumn } from '@document-explorer/react';
<DocumentExplorer columns={['name', badgeColumn({ key: 'metadata.status' }), 'size']} />Conventional statuses get a tone with no configuration — approved green,
pending amber, rejected red — with humanised labels. Everything is
overridable: tones remaps a value or takes the decision entirely, format
rewrites the label, variant: 'pill' fills it, and the colours are
--de-tone-* tokens.
Resizable columns
<DocumentExplorer resizableColumns columnWidths={saved} onColumnResize={persist} />width is the starting track, maxWidth caps how far it can be dragged,
minWidth floors it, and resizable: false pins a column entirely.
Drag a handle to resize, double-click to reset. The handle is a focusable
role="separator", so ← / → resize and Enter resets — resizing is not
drag-only. A flexible column keeps a floor so widening a neighbour can never
hide the file names, and once the columns stop fitting the list scrolls
horizontally with the rows widening to match.
onColumnResize fires once on release, not per pointer move, so it is safe to
write straight to storage.
Theming
Every colour, radius and spacing value is a --de-* custom property. Override
the tokens; you should never need to write a rule against a .de-* class.
.my-explorer {
--de-accent: #7c3aed;
--de-radius: 4px;
--de-row-height: 48px;
}Dark mode follows prefers-color-scheme automatically. For an in-app toggle,
set data-de-theme="dark" (or "light" to pin light) on any ancestor.
Accessibility
The list is a role="grid" with a roving tabindex — one tab stop for the
whole list, not one per row. Arrow keys move, Enter opens, Space selects,
Shift+click/arrow extends a range, Ctrl/Cmd+A selects all, Backspace goes
up a folder, and typing jumps to a matching name. A live region announces the
current folder and result counts.
The grid is a role="listbox" of options, and the tree a role="tree". All
three are checked with axe in CI alongside manual keyboard passes.
Headless use
const { store, snapshot } = useDocumentExplorer({ data, mapper });Every sub-component (ListView, Breadcrumbs, SearchInput, …) is exported,
so you can rebuild the shell and keep the engine.
License
MIT
