snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-23 16:12:52 +05:00
commit a774ac673a
4541 changed files with 770499 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
import { Avatar, type CommentAuthor } from '@tldraw/mentions'
import { Fragment, MouseEvent, ReactNode } from 'react'
import { useTranslation } from 'tldraw'
import { Byline } from './byline'
import { CheckIcon } from './icons'
import { Reactions, type ReactionSummary } from './reactions'
import { replyCountLabel } from './reply-count'
/** @public */
export interface CommentListItemProps {
id: string
author: CommentAuthor
/** A short preview of the thread — e.g. the first comment's body. */
preview: ReactNode
/** ISO datetime of the thread's first comment. */
date: string
resolved?: boolean
/** Name of the page the thread lives on, shown as a small label. Omit to hide. */
page?: string
/** Total comments in the thread. */
count?: number
/** Whether this thread is the open one. */
selected?: boolean
/** Tallied reactions for the row (a thread's, or a single comment's when the row is one
* comment), shown as inert pills under the preview. Omit to hide. */
reactions?: ReactionSummary[]
/**
* Link target for the item. When set, the row renders as an anchor so browser affordances
* (ctrl/cmd-click, middle-click) open it in a new tab; a plain click still calls `onSelect`.
*/
href?: string
}
/**
* What a row is rendered with: the item, plus the list-level wiring it needs to be interactive.
* A custom row gets the same props the default `<CommentListItem>` does, so it can wrap the
* default rather than reimplement it.
*
* @public
*/
export interface CommentListItemRenderProps extends CommentListItemProps {
/** Label for a resolved thread's marker on its row. */
resolvedLabel?: string
/** Called with the thread id when the row is chosen. */
onSelect?(id: string): void
}
/** @public */
export interface CommentsListProps {
items: CommentListItemProps[]
/** Called with a thread id when an item is chosen. */
onSelect?(id: string): void
/** Shown above the list (e.g. "Comments"). Omit for none. */
header?: ReactNode
/** Rendered at the right of the header row — e.g. a filter menu. */
headerAction?: ReactNode
/** Shown in place of the list when there are no threads. */
empty?: ReactNode
/** Label for a resolved thread's marker on its row. Defaults to "Resolved". */
resolvedLabel?: string
/**
* Override how each item renders. Defaults to `<CommentListItem>`, which is exported — so a
* row that only adds something can spread these props into it rather than start over. The list
* supplies the key, so a custom row doesn't need one.
*/
renderItem?(props: CommentListItemRenderProps): ReactNode
}
/**
* A scrollable list of comment threads — each an avatar, byline, and a one-line preview.
* Presentational: you supply the items (already summarised) and an `onSelect` handler; the canvas
* `CanvasCommentsSidebar` wires it to the store, but a consumer can build their own list from this.
* @public @react
*/
export function CommentsList({
items,
onSelect,
header,
headerAction,
empty,
resolvedLabel,
renderItem,
}: CommentsListProps) {
return (
<div className="tlui-cmt-list">
{(header !== undefined || headerAction) && (
<div className="tlui-cmt-list__header">
{header !== undefined && <span className="tlui-cmt-list__header-title">{header}</span>}
{headerAction}
</div>
)}
{items.length === 0 ? (
<div className="tlui-cmt-list__empty">{empty}</div>
) : (
<div className="tlui-cmt-list__items">
{items.map((item) => {
const props: CommentListItemRenderProps = { ...item, resolvedLabel, onSelect }
// A custom row is wrapped rather than keyed directly: it's the consumer's element,
// and requiring them to remember a key is the kind of thing that only shows up as a
// console warning in someone else's app.
return renderItem ? (
<Fragment key={item.id}>{renderItem(props)}</Fragment>
) : (
<CommentListItem key={item.id} {...props} />
)
})}
</div>
)}
</div>
)
}
/** One thread's row in a {@link CommentsList}. @public @react */
export function CommentListItem({
id,
author,
preview,
date,
resolved,
page,
count,
selected,
reactions,
href,
resolvedLabel = 'Resolved',
onSelect,
}: CommentListItemRenderProps) {
const msg = useTranslation()
const handleClick = (e: MouseEvent) => {
if (href && isOpenInNewTabClick(e)) return
e.preventDefault()
if (onSelect) onSelect(id)
}
const Tag = href ? 'a' : 'button'
// `count` is the thread's total comments; a reply is every comment after the opening one.
const replies = count !== undefined ? replyCountLabel(msg, count - 1) : null
return (
<Tag
{...(href ? { href } : { type: 'button' })}
className={
selected ? 'tlui-cmt-list__item tlui-cmt-list__item--selected' : 'tlui-cmt-list__item'
}
data-resolved={resolved || undefined}
onClick={handleClick}
>
<Avatar author={author} />
<div className="tlui-cmt-list__item-body">
<Byline author={author} date={date} />
<div className="tlui-cmt-list__item-preview">{preview}</div>
{reactions && <Reactions reactions={reactions} canReact={false} enableHoverList={false} />}
{(resolved || page !== undefined || replies) && (
<div className="tlui-cmt-list__item-meta">
{resolved && (
<span className="tlui-cmt-list__item-resolved">
<CheckIcon />
{resolvedLabel}
</span>
)}
{page !== undefined && <span className="tlui-cmt-list__item-page">{page}</span>}
{replies && <span className="tlui-cmt-list__item-replies">{replies}</span>}
</div>
)}
</div>
</Tag>
)
}
/**
* Whether a click should be left to the browser's link handling (new tab, new window, or download).
* @public
*/
export function isOpenInNewTabClick(e: MouseEvent) {
return e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0
}