ChatMessageActions

PreviousNext

The replay, edit and copy actions of a user message, revealed when the message is hovered or focused.

Hover this message to reveal its actions.

Last action: none

'use client';

import { useState } from 'react';
import { ChatMessageActions } from '@/ui/chat/ChatMessageActions';

export function ChatMessageActionsDemo() {
  const [lastAction, setLastAction] = useState('none');

  return (
    <div className="w-[380px] flex flex-col items-end gap-8">
      {/* The actions are revealed when the parent `group/message` is hovered or focused */}
      <div className="group/message flex flex-col items-end gap-4">
        <p className="text-b3 bg-lightSand rounded-32 px-24 py-12 text-black">
          Hover this message to reveal its actions.
        </p>
        <ChatMessageActions
          onReplay={() => setLastAction('replay')}
          onEdit={() => setLastAction('edit')}
          onCopy={() => setLastAction('copy')}
        />
      </div>
      <p className="text-b4 text-darkGrey">Last action: {lastAction}</p>
    </div>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/chat-message-actions.json

Usage

ChatMessageActions is already rendered by ChatMessage when you give it onReplay, onEdit or onCopy: you only need this component to build your own message bubble.

The actions are hidden until the message is hovered or focused, so the parent must be a group/message (Tailwind named group). On devices without hover they are always visible.

import { ChatMessageActions } from '@/ui/chat/ChatMessageActions';
 
export function Component() {
  return (
    <div className="group/message flex flex-col items-end gap-4">
      <p className="text-b3 bg-lightSand rounded-32 px-24 py-12">Hello</p>
      <ChatMessageActions
        onReplay={() => console.log('replay')}
        onEdit={() => console.log('edit')}
        onCopy={() => navigator.clipboard.writeText('Hello')}
      />
    </div>
  );
}

Hide an action

A button is rendered only when its callback is provided, and the toolbar itself is empty (but still rendered) when none is. Give only the callbacks you support.

<ChatMessageActions onCopy={() => navigator.clipboard.writeText('Hello')} />

Copy confirmation

Once onCopy succeeded, the copy icon is replaced by a check for 2 seconds, and its label by labels.copied. onCopy may return a promise: the confirmation is shown when it resolves, and nothing is shown when it rejects.

Translate the labels

The labels are used as accessible names and tooltips. Each one is optional and falls back to its English default.

<ChatMessageActions
  onReplay={() => {}}
  onEdit={() => {}}
  onCopy={() => {}}
  labels={{ replay: 'Rejouer', edit: 'Modifier', copy: 'Copier', copied: 'Copié' }}
/>

API Reference

ChatMessageActions

PropTypeDefaultDescription
onReplay() => voidundefinedRenders the replay button
onEdit() => voidundefinedRenders the edit button
onCopy() => void | Promise<void>undefinedRenders the copy button. A check is shown once it succeeded (or resolved)
labelsPartial<ChatMessageLabels>EnglishAccessible names and tooltips of the buttons

ChatMessageLabels

KeyDefaultDescription
replay"Replay"Label of the replay button
edit"Edit"Label of the edit button
copy"Copy"Label of the copy button
copied"Copied"Label of the copy button while the confirmation is shown
cancel"Cancel"Used by ChatMessageEditor
send"Send"Used by ChatMessageEditor

Notes

  • Rendered as a role="toolbar" labelled "Message actions".
  • Revealed with group-hover/message and group-focus-within/message, so keyboard users reach the buttons with Tab. Always visible on (hover: none) devices.
  • The icons are Reload, Edit and Clipboard (CheckDefault once copied) from @clubmed/trident-icons.