Skip to content

Components · Collaboration

LinkedDocsPanel

Docs and links attached to a record: list, open, unlink, link a doc, add a link, filter by Space.

@rookcom/monarch-react/shellComponent

Docs and links on a record

Usage examples

Empty, loading and read-only

Purpose

Records in Rook apps (tickets, tasks, campaigns, audits) link to Rook Dossier docs and to outside pages. LinkedDocsPanel is that panel on Monarch tokens, with the data and the linking left to the product.

Use when

  • On a record’s detail page, in a side column or a "Docs" tab.
  • Read-only (readOnly) for people who may see but not change links.

Avoid when

  • Attachments uploaded to the record itself: use FileUpload and UploadQueue.
  • A general list of documents: use a table or list page.

Design principles

  • Data in, intent out: items comes from the product; link, create, add and unlink are callbacks. The panel never fetches.
  • Both kinds in one list: Dossier docs (status, Space, last update) and external links (the host name shows by default).
  • Filter, don’t hide: Space chips appear only when items span several Spaces, and a filtered list says how many more exist.
  • Promise-aware: a returned promise shows progress on the row or button; a rejection becomes an alert.

API reference

import { LinkedDocsPanel, parseLinkUrl } from '@rookcom/monarch-react/shell';
ExportKindRenderingRoot class
LinkedDocsPanelcomponentclient.mn-linked-docs-panel
parseLinkUrlutilityclient–

Forwards its ref to the section. "Link doc" uses EntityPicker from @rookcom/monarch-react.

Also exported: parseLinkUrl (the add-link validator: full http(s) URLs only).

Generated from the TypeScript declarations of packages/react/src/components/collaboration/linked-docs-panel.

Properties

items={null} is loading. Each item has a row id (id); doc rows can carry docId so linked docs are left out of the picker.

"Link doc" appears with onLinkDoc and either docOptions or onSearchDocs; "New doc" with onCreateDoc; "Add link" with onAddLink.

filter: a Space id, "" for all, null for items without a Space.

LinkedDocsPanel

The shared panel of docs and links attached to a record (a ticket, a task, a campaign): Rook Dossier docs and external links, with Space filter chips, open, unlink, link a doc (search picker, New doc) and add a link. The product supplies the items and does the linking; the panel never fetches.

PropTypeDescription
items requiredLinkedDocItem[] | nullWhat is linked to the record. `null` while loading.
childrenReactNode
classNamestring
defaultFilterstring | null
docOptionsComboboxOption[]Doc options for the picker (static list).
errorReactNodeSomething went wrong loading or changing links (shown as an alert).
filterstring | nullThe Space filter (controlled): a Space id, `''` for all, or `null` for no Space.
headingLevel"h2" | "h3" | "h4"Heading level of the title. Default `h3`.
idstring
linkedDocIdsreadonly string[]Doc ids already linked (left out of the picker). Default: the `docId` of the `doc` items.
messagesPartial<LinkedDocsPanelMessages>Interface text, for translation.
noticeReactNodeReplaces the list, e.g. "You don't have access to Rook Dossier in this workspace."
onAddLink((link: { url: string; title: string; }) => unknown)Add an external link. Shows "Add link".
onCreateDoc(() => unknown)"New doc" in the picker footer.
onFilterChange((filter: string | null) => void)
onLinkDoc((docId: string) => unknown)Link the chosen doc (its option value). Shows "Link doc" with `docOptions` or `onSearchDocs`.
onOpen((item: LinkedDocItem, event: MouseEvent<HTMLAnchorElement>) => void)Called when a row is opened (the link still navigates unless you prevent it).
onRetry(() => void)With `error`, shows Try again.
onSearchDocs((query: string, context: ComboboxSearchContext) => Promise<ComboboxOption[]>)Search docs on the server. Already-linked docs are left out of the results.
onUnlink((item: LinkedDocItem) => unknown)Unlink a row. A returned promise shows progress; a rejection shows an error.
readOnlybooleanHide linking, adding and unlinking.
spaceFilterbooleanShow Space filter chips when items span more than one Space. Default true.

Variants

PropValuesDescription
readOnly'false''true'No link, add or unlink controls.
spaceFilter'true''false'Space chips when items span more than one Space.

States

StateExposed asBehaviour
Loadingaria-busy="true"Skeleton rows and a status.
Empty–A short hint (different when read-only).
Filteredaria-pressed="true"On the active chip; "N more in other Spaces. Show all".
Unlinkingdata-busyThe row dims and its button is busy.
Notice–A product message replaces the list, e.g. no access to Dossier.
Error–An alert, with Try again when onRetry is given.

Interaction behaviour

  • Rows open in a new tab (unless external: false); onOpen runs first and may prevent it.
  • Unlink removes the row once the product updates items; focus moves to the next row, the previous one, or the header.
  • Add link opens a small form with the URL focused; a URL that is not full http(s) is refused with a message. The title defaults to the host name.
  • Link doc opens a search list without the docs already linked; "New doc" sits under it.

Keyboard support

KeysAction
TabThrough chips, rows, unlink buttons and actions.
Enter / SpacePress a chip, open a row, unlink, open the picker or form.
↓ / ↑, EnterChoose a doc in the picker (EntityPicker).
EnterAdd the link from the form.
EscapeCancel the form or close the picker; focus returns to its button.

Accessibility

Role
region (section labelled by its heading) with a list
Accessible name
The heading (messages.title, default "Docs") plus the count.
  • Chips are toggle buttons (aria-pressed) in a labelled group.
  • Unlink buttons are named "Unlink <title>"; new-tab rows say so.
  • The URL field reports errors through FieldError (aria-invalid, aria-describedby).
  • Errors are alerts; loading is a status inside an aria-busy region.
  • Security: only http(s) URLs are accepted from the add-link form (no javascript: or data:); items are rendered as text, and new-tab links get rel="noopener noreferrer". The product must still validate URLs and permissions on the server.

Responsive behaviour

  • Fills its column; long titles and meta truncate with an ellipsis.
  • Chips wrap; chips and rows are 44px tall on touch screens.

Theme support

  • Surface, border and muted text tokens; the active chip uses the accent subtle colours.
  • Transitions are removed under reduced motion.

Do and don't

Do

  • Return the promise from your callbacks so the panel can show progress and errors.
  • Pass docId on doc rows so the picker leaves linked docs out.
  • Pass a notice when the person has no access to Dossier in this workspace.

Don't

  • Do not fetch inside the callbacks without handling errors: reject, and the panel shows a message.
  • Do not put file uploads here.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsBadge, Button, Combobox, EntityPicker, Field, Skeleton, Input

Testing coverage

  • Labelled region with a list; new-tab links named with meta; host name for links; status chip.
  • Space chips (pressed), filtered count and Show all; a stale filter shows all; no chips for one Space or when off.
  • Unlink with a labelled button, focus to the next row; failed unlink alert.
  • Link doc from the picker without linked docs; New doc; server search drops linked docs.
  • Add link: validation, title from the host, focus back; Escape cancels.
  • Loading, empty, read-only, notice and error with Try again; onOpen; URL parser; SSR.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/linked-docs-panel; Cross-browser axe: /components/linked-docs-panel

Tests: packages/react/src/components/collaboration/linked-docs-panel