diff --git a/src/pages/docs/chat/react-ui-kit/components.mdx b/src/pages/docs/chat/react-ui-kit/components.mdx index 6542cec8fb..7d74520d5c 100644 --- a/src/pages/docs/chat/react-ui-kit/components.mdx +++ b/src/pages/docs/chat/react-ui-kit/components.mdx @@ -281,7 +281,6 @@ The `MessageInput` component provides a comprehensive text input interface for c |------|-------------| | `onSent` | Callback function triggered when a message is sent | | `placeholder` | Placeholder text displayed in the input field when empty | -| `className` | Additional CSS class names to apply to the input container | | `enableTyping` | Whether to enable typing indicators on user input | | `onSendError` | Callback triggered when an error occurs while sending a message | @@ -300,18 +299,15 @@ The MessageInput component automatically triggers typing indicators when the use ```react import { MessageInput } from '@ably/chat-react-ui-kit'; -import { useMessages } from '@ably/chat/react'; - -// Basic usage -const { sendMessage } = useMessages(); +import { Message } from '@ably/chat'; -const handleSendMessage = (text: string) => { - console.log(`Sending message: ${text}`); - sendMessage({ text }); +// Basic usage - onSent receives the sent Message object +const handleMessageSent = (message: Message) => { + console.log(`Message sent with serial: ${message.serial}`); }; ``` @@ -362,40 +358,44 @@ import { Sidebar } from '@ably/chat-react-ui-kit'; import { RoomOptions } from '@ably/chat'; import { useState } from 'react'; -const [rooms, setRooms] = useState(['general', 'random']); -const [activeRoom, setActiveRoom] = useState('general'); -const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); -const [roomOptions] = useState({ occupancy: { enableEvents: true } }); - -const addRoom = (name: string) => { - console.log(`Adding room: ${name}`); - setRooms(prev => prev.includes(name) ? prev : [...prev, name]); -}; - -const leaveRoom = (name: string) => { - console.log(`Leaving room: ${name}`); - setRooms(prev => prev.filter(n => n !== name)); - if (activeRoom === name) { - setActiveRoom(rooms.length > 0 ? rooms[0] : undefined); - } -}; - -const handleSetActiveRoom = (name?: string) => { - console.log(`Setting active room to: ${name}`); - setActiveRoom(name); -}; +function SidebarExample() { + const [rooms, setRooms] = useState(['general', 'random']); + const [activeRoom, setActiveRoom] = useState('general'); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); + const [roomOptions] = useState({ occupancy: { enableEvents: true } }); + + const addRoom = (name: string) => { + console.log(`Adding room: ${name}`); + setRooms(prev => prev.includes(name) ? prev : [...prev, name]); + }; + + const leaveRoom = (name: string) => { + console.log(`Leaving room: ${name}`); + setRooms(prev => prev.filter(n => n !== name)); + if (activeRoom === name) { + setActiveRoom(rooms.length > 0 ? rooms[0] : undefined); + } + }; + + const handleSetActiveRoom = (name?: string) => { + console.log(`Setting active room to: ${name}`); + setActiveRoom(name); + }; - setIsSidebarCollapsed(prev => !prev)} -/> - ``` + return ( + setIsSidebarCollapsed(prev => !prev)} + /> + ); +} +``` Rooms are automatically attached when the Sidebar is mounted, and detached when the component unmounts. @@ -453,48 +453,50 @@ import { ChatMessageList } from '@ably/chat-react-ui-kit'; import { Message } from '@ably/chat'; import { useState } from 'react'; -// Example with useState for messages -const [messages, setMessages] = useState([]); -const [isLoadingHistory, setIsLoadingHistory] = useState(false); -const [hasMoreMessages, setHasMoreMessages] = useState(true); +function ChatMessageListExample() { + const [messages, setMessages] = useState([]); + const [isLoadingHistory, setIsLoadingHistory] = useState(false); + const [hasMoreMessages, setHasMoreMessages] = useState(true); -const fetchPreviousMessages = () => { - console.log('Loading more message history'); - setIsLoadingHistory(true); - // Fetch logic would go here - setIsLoadingHistory(false); -}; + const fetchPreviousMessages = () => { + console.log('Loading more message history'); + setIsLoadingHistory(true); + // Fetch logic would go here + setIsLoadingHistory(false); + }; -const handleEditMessage = (message: Message, newText: string) => { - console.log(`Editing message with serial: ${message.serial}, setting text to: ${newText}`); -}; + const handleEditMessage = (message: Message, newText: string) => { + console.log(`Editing message with serial: ${message.serial}, setting text to: ${newText}`); + }; -const handleDeleteMessage = (message: Message) => { - console.log(`Deleting message with serial: ${message.serial}`); -}; + const handleDeleteMessage = (message: Message) => { + console.log(`Deleting message with serial: ${message.serial}`); + }; -const handleAddReaction = (message: Message, emoji: string) => { - console.log(`Adding reaction ${emoji} to message with serial: ${message.serial}`); -}; + const handleAddReaction = (message: Message, emoji: string) => { + console.log(`Adding reaction ${emoji} to message with serial: ${message.serial}`); + }; -const handleRemoveReaction = (message: Message, emoji: string) => { - console.log(`Removing reaction ${emoji} from message with serial: ${message.serial}`); -}; + const handleRemoveReaction = (message: Message, emoji: string) => { + console.log(`Removing reaction ${emoji} from message with serial: ${message.serial}`); + }; - - + return ( + + ); +} ``` @@ -529,7 +531,7 @@ The `ChatMessage` component displays an individual chat message with interactive ```react import { ChatMessage } from '@ably/chat-react-ui-kit'; -import { Message } from '@ably/chat'; +import { Message, MessageReactionType } from '@ably/chat'; import { useMessages } from '@ably/chat/react'; const { updateMessage, deleteMessage, sendReaction, deleteReaction } = useMessages(); @@ -538,19 +540,24 @@ const { updateMessage, deleteMessage, sendReaction, deleteReaction } = useMessag message={message} onEdit={(message: Message, newText: string) => { console.log(`Editing message with serial: ${message.serial}, setting text to: ${newText}`); - updateMessage(message.serial, { text: newText }); + // Use message.copy() to create an updated message with the new text + const updated = message.copy({ text: newText, metadata: message.metadata, headers: message.headers }); + updateMessage(message.serial, updated); }} onDelete={(message: Message) => { console.log(`Deleting message with serial: ${message.serial}`); - deleteMessage(message.serial); + // Pass an optional description for the deletion + deleteMessage(message.serial, { description: 'deleted by user' }); }} onReactionAdd={(message: Message, emoji: string) => { console.log(`Adding reaction ${emoji} to message with serial: ${message.serial}`); - sendReaction(message.serial, { name: emoji }); + // Include the reaction type (e.g., MessageReactionType.Distinct) + sendReaction(message.serial, { type: MessageReactionType.Distinct, name: emoji }); }} onReactionRemove={(message: Message, emoji: string) => { console.log(`Removing reaction ${emoji} from message with serial: ${message.serial}`); - deleteReaction(message.serial, { name: emoji }); + // Include the reaction type when removing + deleteReaction(message.serial, { type: MessageReactionType.Distinct, name: emoji }); }} /> ``` @@ -624,17 +631,22 @@ The Avatar component is designed to integrate with the `AvatarProvider` to displ ```react import { Avatar, useUserAvatar } from '@ably/chat-react-ui-kit'; -const { userAvatar } = useUserAvatar({ + +function UserAvatarExample() { + const { userAvatar } = useUserAvatar({ clientId: 'user-123', displayName: 'John Doe', }); - + return ( + + ); +} ``` @@ -680,25 +692,30 @@ import { ParticipantList } from '@ably/chat-react-ui-kit'; import { useChatClient, usePresence, useTyping } from '@ably/chat/react'; import { useState } from 'react'; -// Integration with presence and typing hooks -const { presenceData } = usePresence(); -const { currentlyTyping } = useTyping(); -const { clientId } = useChatClient(); -const [participantListOpen, setParticipantListOpen] = useState(false); +function ParticipantListExample() { + const { presenceData } = usePresence(); + const { currentlyTyping } = useTyping(); + const { clientId } = useChatClient(); + const [participantListOpen, setParticipantListOpen] = useState(false); -const toggleParticipantList = () => { - console.log('Toggling participant list'); - setParticipantListOpen(prev => !prev); -}; + const toggleParticipantList = () => { + console.log('Toggling participant list'); + setParticipantListOpen(prev => !prev); + }; -{participantListOpen && ( - -)} + return ( + <> + {participantListOpen && ( + + )} + + ); +} ``` diff --git a/src/pages/docs/chat/react-ui-kit/providers.mdx b/src/pages/docs/chat/react-ui-kit/providers.mdx index 87519c7421..3748b3d711 100644 --- a/src/pages/docs/chat/react-ui-kit/providers.mdx +++ b/src/pages/docs/chat/react-ui-kit/providers.mdx @@ -87,8 +87,10 @@ const globalSettings = { const roomSettings = { 'general': { allowMessageUpdatesOwn: true }, 'announcements': { - allowMessageUpdates: false, - allowMessageDeletes: false, + allowMessageUpdatesOwn: false, + allowMessageUpdatesAny: false, + allowMessageDeletesOwn: false, + allowMessageDeletesAny: false, }, }; @@ -123,7 +125,7 @@ const { getEffectiveSettings } = useChatSettings(); const roomSettings = getEffectiveSettings('general'); // Check if message editing is allowed in this room -if (roomSettings.allowMessageUpdates) { +if (roomSettings.allowMessageUpdatesOwn || roomSettings.allowMessageUpdatesAny) { // Show edit button } ``` @@ -328,15 +330,20 @@ The `useAvatar` hook provides access to the avatar context with comprehensive av | Property | Description | |----------|-------------| -| `getAvatarForUser` | Get avatar for a user | -| `getAvatarForRoom` | Get avatar for a room | -| `setUserAvatar` | Set avatar for a user | -| `setRoomAvatar` | Set avatar for a room | -| `resetUserAvatar` | Reset avatar for a user | -| `resetRoomAvatar` | Reset avatar for a room | -| `exportAvatars` | Export all avatars as a JSON string | -| `importAvatars` | Import avatars from a JSON string | -| `onAvatarChange` | Register an avatar change callback | +| `getAvatarForUser` | Get avatar for a user if it exists in cache (returns `AvatarData \| undefined`) | +| `createAvatarForUser` | Create avatar for a user and add to cache (returns `AvatarData`) | +| `getAvatarForRoom` | Get avatar for a room if it exists in cache (returns `AvatarData \| undefined`) | +| `createAvatarForRoom` | Create avatar for a room and add to cache (returns `AvatarData`) | +| `setUserAvatar` | Update an existing user avatar or create a new one | +| `setRoomAvatar` | Update an existing room avatar or create a new one | +| `getUserAvatars` | Get all cached user avatars | +| `getRoomAvatars` | Get all cached room avatars | +| `clearUserAvatars` | Clear all user avatars from cache | +| `clearRoomAvatars` | Clear all room avatars from cache | +| `clearAllAvatars` | Clear all avatars from cache | +| `exportAvatars` | Export all avatar data for backup/migration | +| `importAvatars` | Import avatar data from backup/migration | +| `onAvatarChange` | Register a callback for avatar change events | ### Examples @@ -345,13 +352,26 @@ You can use the `useAvatar` hook to manage user and room avatars in your chat ap ```react -import { useState, useCallback } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useAvatar } from '@ably/chat-react-ui-kit'; -// Basic usage +// Basic usage - getAvatarForUser returns undefined if avatar doesn't exist function UserAvatar({ clientId, displayName }) { - const { getAvatarForUser } = useAvatar(); - const avatar = getAvatarForUser(clientId, displayName); + const { getAvatarForUser, createAvatarForUser } = useAvatar(); + const [avatar, setAvatar] = useState(undefined); + + // Use useEffect to avoid state mutations during render + useEffect(() => { + const existingAvatar = getAvatarForUser(clientId); + if (existingAvatar) { + setAvatar(existingAvatar); + } else { + const newAvatar = createAvatarForUser(clientId, displayName); + setAvatar(newAvatar); + } + }, [getAvatarForUser, createAvatarForUser, clientId, displayName]); + + if (!avatar) return null; return (
@@ -370,14 +390,23 @@ function UserAvatar({ clientId, displayName }) { // Setting a user avatar function UserAvatarManager() { - const { getAvatarForUser, setUserAvatar } = useAvatar(); + const { getAvatarForUser, createAvatarForUser, setUserAvatar } = useAvatar(); const [userId] = useState('user-123'); const [displayName] = useState('John Doe'); - - const avatar = getAvatarForUser(userId, displayName); - - const handleAddUser = useCallback(() => { - // When adding a new user, set their avatar with custom properties + const [avatar, setAvatar] = useState(undefined); + + useEffect(() => { + const existingAvatar = getAvatarForUser(userId); + if (existingAvatar) { + setAvatar(existingAvatar); + } else { + const newAvatar = createAvatarForUser(userId, displayName); + setAvatar(newAvatar); + } + }, [getAvatarForUser, createAvatarForUser, userId, displayName]); + + const handleUpdateAvatar = useCallback(() => { + // Update the user's avatar with custom properties setUserAvatar(userId, { color: 'bg-blue-500', initials: 'JD', @@ -385,6 +414,8 @@ function UserAvatarManager() { }); }, [setUserAvatar, userId]); + if (!avatar) return null; + return (
@@ -403,10 +434,10 @@ function UserAvatarManager() {