diff --git a/demo/package-lock.json b/demo/package-lock.json index 0057ca44e..e79c657c9 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -8,8 +8,9 @@ "name": "my-vue-app", "version": "0.0.0", "dependencies": { - "@ably/chat": "file:..", + "@ably/chat": "^0.7.0", "ably": "^2.9.0", + "ably-chat-react-ui-components": "github:ably/ably-chat-react-ui-components#main", "clsx": "^2.1.1", "nanoid": "^5.1.3", "react": "^18.3.1", @@ -34,55 +35,16 @@ "vite": "^6.2.1" } }, - "..": { - "name": "@ably/chat", + "node_modules/@ably/chat": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@ably/chat/-/chat-0.7.0.tgz", + "integrity": "sha512-m7gChuLoXcB2KWDAwyLBfh+V5YJf3v5aweml+zdIwt5/JNztChmolN4pqJhEv0B5dT21tQU/fWkFtcyXBFJOvQ==", "license": "Apache-2.0", "dependencies": { "async-mutex": "^0.5.0", "dequal": "^2.0.3", "lodash.clonedeep": "^4.5.0" }, - "devDependencies": { - "@eslint/compat": "^1.2.7", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "^9.12.0", - "@testing-library/react": "^16.2.0", - "@types/jsonwebtoken": "^9.0.9", - "@types/lodash.clonedeep": "^4.5.9", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", - "@typescript-eslint/eslint-plugin": "^8.26.1", - "@typescript-eslint/parser": "^8.26.1", - "@vitejs/plugin-react": "^4.3.4", - "@vitest/coverage-v8": "^3.0.8", - "aws-sdk": "^2.1692.0", - "cspell": "^8.17.5", - "eslint": "^9.22.0", - "eslint-plugin-compat": "^6.0.2", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsdoc": "^50.6.6", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.19", - "eslint-plugin-security": "^3.0.1", - "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unicorn": "^57.0.0", - "globals": "^16.0.0", - "jsdom": "^26.0.0", - "jsonwebtoken": "^9.0.2", - "minimist": "^1.2.8", - "prettier": "^3.5.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "typedoc": "^0.28.4", - "typedoc-plugin-no-inherit": "^1.5.0", - "typescript": "^5.8.2", - "vite": "^6.2.1", - "vite-plugin-dts": "^4.5.3", - "vitest": "^3.0.8" - }, "engines": { "node": ">=18.0.0" }, @@ -104,10 +66,6 @@ } } }, - "node_modules/@ably/chat": { - "resolved": "..", - "link": true - }, "node_modules/@ably/msgpack-js": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.4.0.tgz", @@ -1261,7 +1219,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1443,7 +1400,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2233,6 +2189,20 @@ } } }, + "node_modules/ably-chat-react-ui-components": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/ably/ably-chat-react-ui-components.git#158331aba0cde5f73e5003f4f9182dcf51fe398d", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1", + "react-icons": "^5.5.0" + }, + "peerDependencies": { + "@ably/chat": "^0.7.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2310,6 +2280,15 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -4175,6 +4154,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -21046,6 +21031,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/demo/package.json b/demo/package.json index 457280b40..fbd95b505 100644 --- a/demo/package.json +++ b/demo/package.json @@ -13,8 +13,9 @@ "preview": "vite preview" }, "dependencies": { - "@ably/chat": "file:..", + "@ably/chat": "^0.7.0", "ably": "^2.9.0", + "ably-chat-react-ui-components": "github:ably/ably-chat-react-ui-components#main", "clsx": "^2.1.1", "nanoid": "^5.1.3", "react": "^18.3.1", diff --git a/demo/src/App.css b/demo/src/App.css index bb95243d2..d4b507858 100644 --- a/demo/src/App.css +++ b/demo/src/App.css @@ -1,22 +1 @@ @import 'tailwindcss'; - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 042a8c9d6..30b961cd1 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -1,13 +1,13 @@ import { FC, useEffect, useState } from 'react'; import { ChatRoomProvider } from '@ably/chat/react'; import { ReactionTypeProvider } from './containers/ReactionTypeProvider'; -import { UserPresenceComponent } from './components/UserPresenceComponent'; import './App.css'; import { ConnectionStatusComponent } from './components/ConnectionStatusComponent'; import { RoomStatusComponent } from './components/RoomStatusComponent'; -import { ChatBoxComponent } from './components/ChatBoxComponent'; -import { ReactionComponent } from './components/ReactionComponent'; import { OccupancyComponent } from './components/OccupancyComponent'; +import { UserPresenceComponent } from './components/UserPresenceComponent'; +import { ReactionComponent } from './components/ReactionComponent'; +import { ChatWindow } from 'ably-chat-react-ui-components'; // We read the roomID from the URL query string and default to 'abcd' if none // provided. We make sure the URL is updated to always include the roomId. This @@ -60,30 +60,29 @@ const App: FC = () => { attach={true} // Attach to the room automatically when mounted options={{ occupancy: { enableEvents: true } }} // Enable occupancy events > -
-
-
+
+
+
-
+
- -
-
-
-
- - -
+
+
+
+ +
-
+
-
- +
+
+ +
diff --git a/demo/src/components/ChatBoxComponent/ChatBoxComponent.tsx b/demo/src/components/ChatBoxComponent/ChatBoxComponent.tsx deleted file mode 100644 index 4c88ceb91..000000000 --- a/demo/src/components/ChatBoxComponent/ChatBoxComponent.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { MessageComponent } from '../MessageComponent'; -import { useChatClient, useMessages } from '@ably/chat/react'; -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { Message, MessageEvent, MessageEvents, PaginatedResult } from '@ably/chat'; -import { ErrorInfo } from 'ably'; -import { useReactionType } from '../MessageReactions'; -import { MessageInput } from '../MessageInput'; - -interface ChatBoxComponentProps {} - -export const ChatBoxComponent: FC = () => { - const [loading, setLoading] = useState(true); - const [messages, setMessages] = useState([]); - const chatClient = useChatClient(); - const clientId = chatClient.clientId; - - const { getPreviousMessages, deleteMessage, update, addReaction, deleteReaction } = useMessages({ - listener: (event: MessageEvent) => { - const message = event.message; - switch (event.type) { - case MessageEvents.Created: { - setMessages((prevMessages) => { - // if already exists do nothing - const index = prevMessages.findIndex((other) => message.isSameAs(other)); - if (index !== -1) { - return prevMessages; - } - - // if the message is not in the list, make a new list that contains it - const newArray = [...prevMessages, message]; - - // and put it at the right place - newArray.sort((a, b) => (a.before(b) ? -1 : 1)); - - return newArray; - }); - break; - } - case MessageEvents.Updated: - case MessageEvents.Deleted: { - setMessages((prevMessages) => { - const index = prevMessages.findIndex((other) => message.isSameAs(other)); - if (index === -1) { - return prevMessages; - } - - const newMessage = prevMessages[index].with(event); - - // if no change, do nothing - if (newMessage === prevMessages[index]) { - return prevMessages; - } - - // copy array and replace the message - const updatedArray = prevMessages.slice(); - updatedArray[index] = newMessage; - return updatedArray; - }); - break; - } - default: { - console.error('Unknown message', message); - } - } - }, - reactionsListener: (reaction) => { - const messageSerial = reaction.summary.messageSerial; - setMessages((prevMessages) => { - const index = prevMessages.findIndex((m) => m.serial === messageSerial); - if (index === -1) { - return prevMessages; - } - - const newMessage = prevMessages[index].with(reaction); - - // if no change, do nothing - if (newMessage === prevMessages[index]) { - return prevMessages; - } - - // copy array and replace the message - const updatedArray = prevMessages.slice(); - updatedArray[index] = newMessage; - return updatedArray; - }); - }, - onDiscontinuity: (discontinuity) => { - console.log('Discontinuity', discontinuity); - // reset the messages when a discontinuity is detected, - // this will trigger a re-fetch of the messages - setMessages([]); - - // set our state to loading, because we'll need to fetch previous messages again - setLoading(true); - - // Do a message backfill - backfillPreviousMessages(getPreviousMessages); - }, - }); - - const backfillPreviousMessages = (getPreviousMessages: ReturnType['getPreviousMessages']) => { - if (getPreviousMessages) { - getPreviousMessages({ limit: 50 }) - .then((result: PaginatedResult) => { - setMessages(result.items.reverse()); - setLoading(false); - }) - .catch((error: ErrorInfo) => { - console.error(`Failed to backfill previous messages: ${error.toString()}`, error); - }); - } - }; - - const handleRESTMessageUpdate = (updatedMessage: Message) => { - setMessages((prevMessages) => { - const index = prevMessages.findIndex((m) => m.serial === updatedMessage.serial); - if (index === -1) { - return prevMessages; - } - if (updatedMessage.version <= prevMessages[index].version) { - return prevMessages; - } - const updatedArray = prevMessages.slice(); - updatedArray[index] = updatedMessage; - return updatedArray; - }); - }; - - const onUpdateMessage = useCallback( - (message: Message) => { - const newText = prompt('Enter new text'); - if (!newText) { - return; - } - update( - message.copy({ - text: newText, - metadata: message.metadata, - headers: message.headers, - }), - ) - .then((updatedMessage: Message) => { - handleRESTMessageUpdate(updatedMessage); - }) - .catch((error: unknown) => { - console.warn('Failed to update message', error); - }); - }, - [update], - ); - - const onDeleteMessage = useCallback( - (message: Message) => { - deleteMessage(message, { description: 'deleted by user' }).then((deletedMessage: Message) => { - handleRESTMessageUpdate(deletedMessage); - }); - }, - [deleteMessage], - ); - - // Used to anchor the scroll to the bottom of the chat - const messagesEndRef = useRef(null); - - useEffect(() => { - console.debug('updating getPreviousMessages useEffect', { getPreviousMessages }); - backfillPreviousMessages(getPreviousMessages); - }, [getPreviousMessages]); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; - - useEffect(() => { - if (!loading) { - scrollToBottom(); - } - }, [messages, loading]); - - const reactionType = useReactionType(); - - return ( -
- {loading &&
loading...
} - {!loading && ( -
-
- {messages.map((msg) => ( - - ))} -
-
- -
- )} -
- ); -}; diff --git a/demo/src/components/ChatBoxComponent/index.ts b/demo/src/components/ChatBoxComponent/index.ts deleted file mode 100644 index e79087b26..000000000 --- a/demo/src/components/ChatBoxComponent/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ChatBoxComponent } from './ChatBoxComponent.tsx'; diff --git a/demo/src/components/MessageComponent/MessageComponent.tsx b/demo/src/components/MessageComponent/MessageComponent.tsx deleted file mode 100644 index 42a478435..000000000 --- a/demo/src/components/MessageComponent/MessageComponent.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { Message, MessageReactionType, Messages } from '@ably/chat'; -import { useChatClient } from '@ably/chat/react'; -import React, { useCallback } from 'react'; -import clsx from 'clsx'; -import { FaPencil, FaTrash } from 'react-icons/fa6'; -import { MessageReactionsUnique, MessageReactionsDistinct, MessageReactionsMultiple } from '../MessageReactions'; - -interface MessageProps { - self?: boolean; - message: Message; - - reactionType?: MessageReactionType; - - onMessageUpdate?(message: Message): void; - - onMessageDelete?(msg: Message): void; - - onReactionAdd?: Messages['reactions']['add']; - - onReactionDelete?: Messages['reactions']['delete']; -} - -const shortDateTimeFormatter = new Intl.DateTimeFormat('default', { - hour: '2-digit', - minute: '2-digit', -}); - -const shortDateFullFormatter = new Intl.DateTimeFormat('default', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', -}); - -function shortDate(date: Date): string { - if (Date.now() - date.getTime() < 1000 * 60 * 60 * 24) { - return shortDateTimeFormatter.format(date); - } - return shortDateFullFormatter.format(date); -} - -export const MessageComponent: React.FC = ({ - self = false, - message, - reactionType = MessageReactionType.Distinct, - onMessageUpdate, - onMessageDelete, - onReactionAdd, - onReactionDelete, -}) => { - const client = useChatClient(); - const clientId = client.clientId; - - const handleMessageUpdate = useCallback( - (e: React.UIEvent) => { - e.stopPropagation(); - onMessageUpdate?.(message); - }, - [message, onMessageUpdate], - ); - - const handleMessageDelete = useCallback( - (e: React.UIEvent) => { - e.stopPropagation(); - onMessageDelete?.(message); - }, - [message, onMessageDelete], - ); - - let reactionsUI = <>; - - if (onReactionAdd && onReactionDelete) { - switch (reactionType) { - case MessageReactionType.Unique: { - reactionsUI = ( - - ); - break; - } - case MessageReactionType.Distinct: { - reactionsUI = ( - - ); - break; - } - case MessageReactionType.Multiple: { - reactionsUI = ( - - ); - break; - } - } - } - - return ( -
-
-
-
- {message.clientId} ·{' '} - - {shortDate(message.createdAt)} - {message.createdAt.toLocaleString()} - - {message.isUpdated && message.updatedAt ? ( - <> - {' '} - · Edited{' '} - - {shortDate(message.updatedAt)} - {message.updatedAt.toLocaleString()} - - {message.updatedBy ? by {message.updatedBy} : ''} - - ) : ( - '' - )} -
-
- {message.isDeleted ? ( - <> - This message was deleted. - { - e.preventDefault(); - onMessageUpdate?.(message); - }} - > - Edit - - - ) : ( - message.text - )} -
-
- - - {reactionsUI} -
-
-
-
- ); -}; diff --git a/demo/src/components/MessageComponent/index.ts b/demo/src/components/MessageComponent/index.ts deleted file mode 100644 index 8a6911a38..000000000 --- a/demo/src/components/MessageComponent/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MessageComponent } from './MessageComponent.tsx'; diff --git a/demo/src/components/MessageInput/MessageInput.tsx b/demo/src/components/MessageInput/MessageInput.tsx deleted file mode 100644 index 0200b922e..000000000 --- a/demo/src/components/MessageInput/MessageInput.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { ChangeEventHandler, FC, FormEventHandler, useEffect, useRef, useState } from 'react'; -import { useChatConnection, useMessages, useTyping } from '@ably/chat/react'; -import { ConnectionStatus } from '@ably/chat'; -import { TypingIndicatorPanel } from '../TypingIndicatorPanel'; -import { SettingsModal } from '../SettingsModal'; - -interface MessageInputProps {} - -export const MessageInput: FC = ({}) => { - const { send } = useMessages(); - const { keystroke, stop } = useTyping(); - const { currentStatus } = useChatConnection(); - const [shouldDisable, setShouldDisable] = useState(true); - - useEffect(() => { - // disable the input if the connection is not established - setShouldDisable(currentStatus !== ConnectionStatus.Connected); - }, [currentStatus]); - - const handleStartTyping = () => { - keystroke().catch((error: unknown) => { - console.error('Failed to start typing indicator', error); - }); - }; - const handleStopTyping = () => { - stop().catch((error: unknown) => { - console.error('Failed to stop typing indicator', error); - }); - }; - - const handleValueChange: ChangeEventHandler = ({ target }) => { - // Typing indicators start method should be called with every keystroke since - // they automatically stop if the user stops typing for a certain amount of time. - // - // The timeout duration can be configured when initializing the room. - if (target.value && target.value.length > 0) { - handleStartTyping(); - } else { - // For good UX we should stop typing indicators as soon as the input field is empty. - handleStopTyping(); - } - }; - - // Keep a reference to the input element to read it and reset it after sending the message - const messageInputRef = useRef(null); - - const handleFormSubmit: FormEventHandler = (event) => { - event.preventDefault(); - event.stopPropagation(); - - // do nothing in case we don't have a ref to the input element - if (!messageInputRef.current) { - return; - } - - // send the message and reset the input field - send({ text: messageInputRef.current.value }) - .then(() => { - if (messageInputRef.current) { - messageInputRef.current.value = ''; - } - }) - .catch((error: unknown) => { - console.error('Failed to send message', error); - }); - - // stop typing indicators - handleStopTyping(); - }; - - return ( -
- -
- - - - -
- ); -}; diff --git a/demo/src/components/MessageInput/index.ts b/demo/src/components/MessageInput/index.ts deleted file mode 100644 index aea336682..000000000 --- a/demo/src/components/MessageInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MessageInput } from './MessageInput.tsx'; diff --git a/demo/src/components/MessageReactionTypeSelector/MessageReactionTypeSelector.tsx b/demo/src/components/MessageReactionTypeSelector/MessageReactionTypeSelector.tsx deleted file mode 100644 index 595827179..000000000 --- a/demo/src/components/MessageReactionTypeSelector/MessageReactionTypeSelector.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { useReactionType } from '../MessageReactions'; -import { MessageReactionType } from '@ably/chat'; - -export const MessageReactionTypeSelector: React.FC = () => { - const { type, setType } = useReactionType(); - - return ( -
-

Message reactions type

- -
- ); -}; diff --git a/demo/src/components/MessageReactionTypeSelector/index.ts b/demo/src/components/MessageReactionTypeSelector/index.ts deleted file mode 100644 index b6433e889..000000000 --- a/demo/src/components/MessageReactionTypeSelector/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MessageReactionTypeSelector as MessageReactionTypeSelector } from './MessageReactionTypeSelector.js'; diff --git a/demo/src/components/MessageReactions/MessageReactionsDistinct.tsx b/demo/src/components/MessageReactions/MessageReactionsDistinct.tsx deleted file mode 100644 index b2343eec1..000000000 --- a/demo/src/components/MessageReactions/MessageReactionsDistinct.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { Message, MessageReactionType, Messages } from '@ably/chat'; -import './styles.css'; - -interface MessageReactionsDistinctProps { - message: Message; - clientId: string; - onReactionAdd: Messages['reactions']['add']; - onReactionDelete: Messages['reactions']['delete']; -} - -const emojis = ['👍', '❤️', '🔥', '🚀']; - -export const MessageReactionsDistinct: React.FC = ({ - message, - clientId, - onReactionAdd, - onReactionDelete: onReactionRemove, -}) => { - const distinct = message.reactions.distinct ?? {}; - - const handleReactionClick = (name: string) => { - if (distinct[name]?.clientIds.includes(clientId)) { - onReactionRemove(message, { type: MessageReactionType.Distinct, name: name }); - } else { - onReactionAdd(message, { type: MessageReactionType.Distinct, name: name }); - } - }; - - const currentEmojis = emojis.slice(); - for (const emoji in distinct) { - if (!currentEmojis.includes(emoji)) { - currentEmojis.push(emoji); - } - } - - return ( - <> - {currentEmojis.map((name) => ( - - ))} - - ); -}; diff --git a/demo/src/components/MessageReactions/MessageReactionsMultiple.tsx b/demo/src/components/MessageReactions/MessageReactionsMultiple.tsx deleted file mode 100644 index dc008ebce..000000000 --- a/demo/src/components/MessageReactions/MessageReactionsMultiple.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Message, MessageReactionType, Messages } from '@ably/chat'; -import './styles.css'; - -interface MessageReactionsMultipleProps { - message: Message; - onReactionAdd: Messages['reactions']['add']; - onReactionDelete: Messages['reactions']['delete']; -} - -const emojis = ['👍', '❤️', '🔥', '🚀']; - -export const MessageReactionsMultiple: React.FC = ({ - message, - onReactionAdd, - onReactionDelete: onReactionRemove, -}) => { - const handleReactionClick = (name: string) => { - onReactionAdd(message, { type: MessageReactionType.Multiple, name: name }); - }; - - const handleReactionRemoveClick = (name: string) => { - onReactionRemove(message, { type: MessageReactionType.Multiple, name: name }); - }; - - const multiple = message.reactions.multiple ?? {}; - - const currentEmojis = emojis.slice(); - for (const emoji in multiple) { - if (!currentEmojis.includes(emoji)) { - currentEmojis.push(emoji); - } - } - - return ( - <> - {currentEmojis.map((name) => ( - - ))} - - ); -}; diff --git a/demo/src/components/MessageReactions/MessageReactionsUnique.tsx b/demo/src/components/MessageReactions/MessageReactionsUnique.tsx deleted file mode 100644 index bd53b05b4..000000000 --- a/demo/src/components/MessageReactions/MessageReactionsUnique.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { Message, MessageReactionType, Messages } from '@ably/chat'; -import './styles.css'; - -interface MessageReactionsUniqueProps { - message: Message; - clientId: string; - onReactionAdd: Messages['reactions']['add']; - onReactionDelete: Messages['reactions']['delete']; -} - -const emojis = ['👍', '❤️', '🔥', '🚀']; - -export const MessageReactionsUnique: React.FC = ({ - message, - clientId, - onReactionAdd, - onReactionDelete: onReactionRemove, -}) => { - const unique = message.reactions.unique ?? {}; - - const handleReactionClick = (name: string) => { - if (unique[name]?.clientIds.includes(clientId)) { - onReactionRemove(message, { type: MessageReactionType.Unique, name: name }); - } else { - onReactionAdd(message, { type: MessageReactionType.Unique, name: name }); - } - }; - - const currentEmojis = emojis.slice(); - for (const emoji in unique) { - if (!currentEmojis.includes(emoji)) { - currentEmojis.push(emoji); - } - } - - return ( - <> - {currentEmojis.map((name) => ( - - ))} - - ); -}; diff --git a/demo/src/components/MessageReactions/index.ts b/demo/src/components/MessageReactions/index.ts deleted file mode 100644 index 85999d295..000000000 --- a/demo/src/components/MessageReactions/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { MessageReactionsUnique } from './MessageReactionsUnique'; -export { MessageReactionsDistinct } from './MessageReactionsDistinct'; -export { MessageReactionsMultiple } from './MessageReactionsMultiple'; - -import { useContext } from 'react'; -import { ReactionTypeContext } from '../../containers/ReactionTypeProvider/reactionTypeProvider'; - -export function useReactionType() { - const context = useContext(ReactionTypeContext); - return context; -} diff --git a/demo/src/components/MessageReactions/styles.css b/demo/src/components/MessageReactions/styles.css deleted file mode 100644 index 30ad35d12..000000000 --- a/demo/src/components/MessageReactions/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -/* Custom styles for message reaction buttons */ -.reaction-button { - font-size: 0.875rem; /* text-sm */ - display: inline-block; - color: black; /* text-black */ - cursor: pointer; - margin: 0; /* m-0 */ - border: 0; /* border-0 */ - background-color: transparent; /* bg-transparent */ - padding: 0; /* p-0 */ -} diff --git a/demo/src/components/SettingsModal/SettingsModal.tsx b/demo/src/components/SettingsModal/SettingsModal.tsx deleted file mode 100644 index 0ba464c26..000000000 --- a/demo/src/components/SettingsModal/SettingsModal.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { MessageReactionTypeSelector } from '../MessageReactionTypeSelector'; -import { FiSettings } from 'react-icons/fi'; - -interface SettingsModalProps { - className?: string; -} - -// This component is used to display a settings modal for message reactions. Message reactions -// have three types: Unique, Distinct, and Multiple which govern the behavior of how reactions are added to messages. -export const SettingsModal: React.FC = ({ className = '' }) => { - const [isOpen, setIsOpen] = useState(false); - const modalRef = useRef(null); - - const toggleModal = () => { - setIsOpen(!isOpen); - }; - - // Close the modal when clicking outside of it - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (modalRef.current && !modalRef.current.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - if (isOpen) { - document.addEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isOpen]); - - return ( -
- - - {isOpen && ( -
-
-
-
-

Reaction Settings

- -
- -
-
- )} -
- ); -}; diff --git a/demo/src/components/SettingsModal/index.ts b/demo/src/components/SettingsModal/index.ts deleted file mode 100644 index a3159c5bf..000000000 --- a/demo/src/components/SettingsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SettingsModal } from './SettingsModal'; diff --git a/demo/src/components/TypingIndicatorPanel/TypingIndicatorPanel.tsx b/demo/src/components/TypingIndicatorPanel/TypingIndicatorPanel.tsx deleted file mode 100644 index cc668f679..000000000 --- a/demo/src/components/TypingIndicatorPanel/TypingIndicatorPanel.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FC } from 'react'; -import { useChatClient, useTyping } from '@ably/chat/react'; - -interface TypingIndicatorPanelProps {} - -export const TypingIndicatorPanel: FC = () => { - const chatClient = useChatClient(); - const clientId = chatClient.clientId; - const { currentlyTyping } = useTyping(); - - // Filter out the current user from the list of typing users - const otherTypingUsers = Array.from(currentlyTyping).filter((client) => client !== clientId); - - return ( -
- {otherTypingUsers.length > 0 && ( -

- {otherTypingUsers.join(', ')} {otherTypingUsers.length > 1 ? 'are' : 'is'} typing... -

- )} -
- ); -}; diff --git a/demo/src/components/TypingIndicatorPanel/index.ts b/demo/src/components/TypingIndicatorPanel/index.ts deleted file mode 100644 index b057c7440..000000000 --- a/demo/src/components/TypingIndicatorPanel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TypingIndicatorPanel } from './TypingIndicatorPanel.tsx'; diff --git a/demo/src/index.css b/demo/src/index.css index 2ba594002..d4b507858 100644 --- a/demo/src/index.css +++ b/demo/src/index.css @@ -1,69 +1 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} - -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +@import 'tailwindcss'; diff --git a/demo/src/main.tsx b/demo/src/main.tsx index c9801c72c..2d1b609db 100644 --- a/demo/src/main.tsx +++ b/demo/src/main.tsx @@ -5,9 +5,9 @@ import { ClientOptions } from 'ably'; import { ChatClient, LogLevel } from '@ably/chat'; import { ChatClientProvider } from '@ably/chat/react'; import { nanoid } from 'nanoid'; -import App from './App.tsx'; import './index.css'; import { AblyProvider } from 'ably/react'; +import App from './App'; // Generate a random clientId and remember it for the length of the session, so // if refreshing the page you still see your own messages as yours.