Files
upstream-tldraw/packages/commenting/src/ui/reaction-picker.tsx
upstream-mirror a774ac673a
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
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

114 lines
3.9 KiB
TypeScript

import { useCallback, useId, type ComponentType, type KeyboardEvent, type MouseEvent } from 'react'
import {
TldrawUiButton,
TldrawUiDropdownMenuContent,
TldrawUiDropdownMenuRoot,
TldrawUiDropdownMenuTrigger,
tlmenus,
useMaybeEditor,
useTranslation,
} from 'tldraw'
import { EmojiPicker, type EmojiPickerProps } from './emoji-picker'
import { SmileyIcon } from './icons'
import { RenderReaction } from './reaction'
/** @public */
export interface ReactionPickerProps {
/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */
emoji?: string[]
/** Emoji the current user has already reacted with; shown as pressed in the grid. */
selected?: string[]
/** Called when an emoji is chosen. */
onSelect?(emoji: string): void
/** How to draw each emoji token. Defaults to the token string (OS emoji font). */
renderReaction?: RenderReaction
/**
* What the button opens — the thing that produces a token. Defaults to `EmojiPicker`, the grid of
* emoji. Swap it for any component taking the same props to offer something else entirely (the
* drawn reactions example replaces it with a canvas you draw in); the props are passed straight
* through either way.
*/
palette?: ComponentType<EmojiPickerProps>
/**
* Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.
* A thread renders one picker per comment, so this must differ per comment — sharing an id
* makes every picker in the thread open at once. Defaults to a per-instance generated id;
* pass one only for a stabler, more debuggable value.
*/
menuId?: string
/** Class for the trigger button. Defaults to the card-action style, matching the ⋯ button. */
className?: string
}
/**
* Whether a palette pick asked to keep the palette open (shift held) for multi-select.
* @internal
*/
export function isMultiSelectPick(event?: MouseEvent | KeyboardEvent): boolean {
return !!event?.shiftKey
}
/**
* The add-reaction affordance: a smiley button that opens an emoji grid.
*
* Anchored to its own button rather than to the reactions row, so the menu keeps its position as
* reactions are added and the row reflows.
*
* Picking an emoji dismisses the grid — adding and removing alike, since either way the picker has
* done its job — and hands focus back to the trigger. Shift-picking keeps the grid open, for
* choosing several reactions in a row.
* @public @react
*/
export function ReactionPicker({
emoji,
selected,
onSelect,
renderReaction,
palette: Palette = EmojiPicker,
menuId,
className = 'tlui-cmt-thread__action',
}: ReactionPickerProps) {
const msg = useTranslation()
const editor = useMaybeEditor()
// Unique per mounted picker unless the host supplies something stabler — see `menuId`.
const generatedId = useId()
const id = menuId ?? `comment-reactions-${generatedId}`
// The dropdown's open state lives in tldraw's global menu registry, so dropping the entry is
// what closes it. The palette's tokens are plain buttons — a swappable component, not radix
// menu items — so nothing dismisses the menu on its own.
const handleSelect = useCallback(
(value: string, event?: MouseEvent | KeyboardEvent) => {
onSelect?.(value)
if (!isMultiSelectPick(event)) {
tlmenus.deleteOpenMenu(id, editor?.contextId)
}
},
[onSelect, id, editor]
)
return (
<TldrawUiDropdownMenuRoot id={id}>
<TldrawUiDropdownMenuTrigger>
<TldrawUiButton
type="icon"
tooltip={msg('comments.add-reaction')}
title={msg('comments.add-reaction')}
className={className}
>
<SmileyIcon />
</TldrawUiButton>
</TldrawUiDropdownMenuTrigger>
{/* left-aligned under the trigger, so the grid opens rightward off the card's edge */}
<TldrawUiDropdownMenuContent side="bottom" align="start" alignOffset={0} sideOffset={4}>
<Palette
emoji={emoji}
selected={selected}
onSelect={handleSelect}
renderReaction={renderReaction}
/>
</TldrawUiDropdownMenuContent>
</TldrawUiDropdownMenuRoot>
)
}