CommentRail
A margin of comment threads beside a document. On a phone it becomes a sheet.
SourcePackage @weft-js/warp
Preview
tsx
import { state } from '@weft-js/core'import { CommentAnchor, CommentRail, Heading, Row, Stack, Text } from '@weft-js/warp'import type { CommentThreadData, Peer } from '@weft-js/warp'export function Document() { const active = state<string | null>('c-timeouts') const threads = state<CommentThreadData[]>([COMMENT_TIMEOUTS, COMMENT_OWNER]) const reply = (id: string, text: string) => (threads.value = threads.value.map(t => (t.id === id ? { ...t, comments: [...t.comments, newComment(text)] } : t))) return ( <Row gap="group" align="start" reflow="tablet"> <Stack gap="related"> <Heading level={3}>Rollback runbook</Heading> <Text>The cache keeps a page for <CommentAnchor thread="c-timeouts" active={active}>60 seconds</CommentAnchor> after a deploy, so a rollback can take a minute to reach everyone.</Text> <Text><CommentAnchor thread="c-owner" active={active}>The person on call</CommentAnchor> decides, and tells support once it is done.</Text> </Stack> <CommentRail threads={threads} active={active} onReply={reply} /> </Row> )}const PEOPLE = [ { id: 'mara', name: 'Mara Lindqvist', email: 'mara@weft.dev', role: 'Owner', team: 'Platform', initials: 'ML' }, { id: 'jun', name: 'Jun Tanaka', email: 'jun@weft.dev', role: 'Admin', team: 'Platform', initials: 'JT' }, { id: 'priya', name: 'Priya Raman', email: 'priya@weft.dev', role: 'Member', team: 'Web', initials: 'PR' }, { id: 'tomas', name: 'Tomás Oliveira', email: 'tomas@weft.dev', role: 'Member', team: 'Web', initials: 'TO' }, { id: 'lena', name: 'Lena Fischer', email: 'lena@weft.dev', role: 'Member', team: 'Data', initials: 'LF' }, { id: 'noor', name: 'Noor Haddad', email: 'noor@weft.dev', role: 'Viewer', team: 'Data', initials: 'NH' }, { id: 'sam', name: 'Sam Okafor', email: 'sam@weft.dev', role: 'Member', team: 'Mobile', initials: 'SO' }, { id: 'ines', name: 'Inés Vidal', email: 'ines@weft.dev', role: 'Viewer', team: 'Mobile', initials: 'IV' },]const PEERS: Peer[] = PEOPLE.map((p, i) => ({ id: p.id, name: p.name, color: i + 1 }))const [MARA, JUN, PRIYA, TOMAS, LENA, NOOR, SAM, INES] = PEERS as [Peer, Peer, Peer, Peer, Peer, Peer, Peer, Peer]const COMMENT_TIMEOUTS: CommentThreadData = { id: 'c-timeouts', comments: [ { id: 'a', author: LENA, time: '10:12', text: 'Should this say 30 seconds? The stale window changed on Friday.' }, { id: 'b', author: JUN, time: '10:20', text: 'Yes. d-4817 lowered it, I will update the number.' }, ],}const COMMENT_OWNER: CommentThreadData = { id: 'c-owner', comments: [{ id: 'c', author: PRIYA, time: 'Mon', text: 'Who signs off on the rollback rule, Mara or the on-call person?' }] }const newComment = (text: string) => ({ id: text, author: MARA, time: 'Now', text })Installation
npm install @weft-js/warpUsage
tsx
import { CommentRail } from '@weft-js/warp'Accessibility
- This has no shortcuts of its own: controls inside it use Tab, Enter and Space as usual, and warp's parts keep their roles and labels.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
threads | Live<CommentThreadData[]> | — | The threads. |
active | State<string | null> | — | The open thread, shared with its CommentAnchor. |
label | string | 'Comments' | Names the landmark. |
| Event | Type | Default | Description |
|---|---|---|---|
onReply | ((thread: string, text: string) => void) | — | Called with the thread and the text. |
onResolve | ((thread: string, resolved: boolean) => void) | — | Called when a thread is resolved or reopened. |
RelatedComment anchorComment thread