UNPKG

@cometchat/chat-uikit-react

Version:

Ready-to-use Chat UI Components for React

874 lines (869 loc) 32.2 kB
import './index.css'; import { CometChatPopover, useCometChatPopoverContext } from './chunk-GI7Y3RZV.js'; import { CometChatAvatar } from './chunk-LDXTHTWH.js'; import { useLocale } from './chunk-UVWJYQJ3.js'; import React, { createContext, useCallback, useContext, useRef, useEffect, useMemo, useReducer, useState } from 'react'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import { CometChat } from '@cometchat/chat-sdk-javascript'; var CometChatReactionsContext = createContext(null); function useCometChatReactionsContext() { const ctx = useContext(CometChatReactionsContext); if (!ctx) { throw new Error("useCometChatReactionsContext must be used within <CometChatReactions.Root>"); } return ctx; } var CometChatReactionsChip = React.memo( function CometChatReactionsChip2({ reaction, className }) { const { onReactionClick } = useCometChatReactionsContext(); const { getLocalizedString } = useLocale(); const emoji = reaction.getReaction(); const count = reaction.getCount(); const reactedByMe = reaction.getReactedByMe(); const handleClick = useCallback(() => { onReactionClick(emoji); }, [emoji, onReactionClick]); const chipClass = [ "cometchat-reactions__chip", reactedByMe ? "cometchat-reactions__chip--active" : "", className ?? "" ].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs( "button", { type: "button", className: chipClass, onClick: handleClick, "aria-label": getLocalizedString("accessibility_reacted_by").replace("{emoji}", emoji).replace("{count}", String(count)), "aria-pressed": reactedByMe, children: [ /* @__PURE__ */ jsx("span", { className: "cometchat-reactions__chip-emoji", children: emoji }), /* @__PURE__ */ jsx("span", { className: "cometchat-reactions__chip-count", children: count }) ] } ); }, (prev, next) => prev.reaction.getReaction() === next.reaction.getReaction() && prev.reaction.getCount() === next.reaction.getCount() && prev.reaction.getReactedByMe() === next.reaction.getReactedByMe() && prev.className === next.className ); var CometChatReactionsOverflow = ({ count, className }) => { const { getLocalizedString } = useLocale(); const overflowClass = ["cometchat-reactions__overflow", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsx( "button", { type: "button", className: overflowClass, "aria-label": getLocalizedString("accessibility_more_reactions").replace( "{count}", String(count) ), children: /* @__PURE__ */ jsxs("span", { className: "cometchat-reactions__overflow-count", children: [ "+", count ] }) } ); }; var INFO_LIMIT = 3; var CometChatReactionsInfo = ({ emoji, className }) => { const { message, reactionsRequestBuilder } = useCometChatReactionsContext(); const { getLocalizedString } = useLocale(); const [names, setNames] = useState([]); const [totalCount, setTotalCount] = useState(0); const [infoState, setInfoState] = useState("idle"); const fetchedRef = useRef(false); useEffect(() => { const reactions = message.getReactions(); const match = reactions.find((r) => r.getReaction() === emoji); setTotalCount(match ? match.getCount() : 0); }, [message, emoji]); const fetchReactorNames = useCallback(async () => { if (fetchedRef.current) return; fetchedRef.current = true; if (!CometChat.isInitialized()) { setInfoState("loaded"); return; } setInfoState("loading"); try { const messageId = message.getId(); if (!messageId) { setInfoState("loaded"); return; } const builder = reactionsRequestBuilder ?? new CometChat.ReactionsRequestBuilder().setLimit(INFO_LIMIT); builder.setMessageId(messageId); builder.setReaction(emoji); const request = builder.build(); const reactors = await request.fetchNext(); let loggedInUid; try { const loggedInUser = await CometChat.getLoggedinUser(); loggedInUid = loggedInUser?.getUid(); } catch { } const fetchedNames = []; for (const reactor of reactors) { const reactedBy = reactor.getReactedBy(); if (loggedInUid && reactedBy.getUid() === loggedInUid) { fetchedNames.unshift(getLocalizedString("reaction_popup_you") || "You"); } else { fetchedNames.push(reactedBy.getName()); } } setNames(fetchedNames); setInfoState("loaded"); } catch { setInfoState("loaded"); } }, [message, emoji, reactionsRequestBuilder, getLocalizedString]); useEffect(() => { void fetchReactorNames(); }, [fetchReactorNames]); const infoClass = ["cometchat-reactions__info", className ?? ""].filter(Boolean).join(" "); const pendingCount = totalCount - names.length; return /* @__PURE__ */ jsx("div", { className: infoClass, role: "tooltip", children: /* @__PURE__ */ jsxs("div", { className: "cometchat-reactions__info-content", children: [ infoState === "loading" && /* @__PURE__ */ jsx("div", { className: "cometchat-reactions__info-loading" }), infoState === "error" && /* @__PURE__ */ jsx("div", { className: "cometchat-reactions__info-error", children: getLocalizedString("error_text") || "Failed to load" }), infoState === "loaded" && /* @__PURE__ */ jsxs("div", { className: "cometchat-reactions__info-emoji-text", children: [ /* @__PURE__ */ jsx("span", { className: "cometchat-reactions__info-emoji", children: emoji }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsxs("div", { className: "cometchat-reactions__info-title", children: [ names.join(", "), pendingCount > 0 && ` ${getLocalizedString("reaction_popup_and") || "and"} ${String(pendingCount)} ${getLocalizedString("reaction_popup_others") || "others"}` ] }), /* @__PURE__ */ jsx("div", { className: "cometchat-reactions__info-description", children: getLocalizedString("reaction_reacted") || "reacted" }) ] }) ] }) ] }) }); }; var CometChatReactionListContext = createContext( null ); function useCometChatReactionListContext() { const ctx = useContext(CometChatReactionListContext); if (!ctx) { throw new Error( "useCometChatReactionListContext must be used within <CometChatReactionList.Root>" ); } return ctx; } var DEFAULT_LIMIT = 20; var CometChatReactionListManager = class { constructor(messageId, builder) { this.request = null; if (!CometChat.isInitialized()) { this.request = null; return; } try { const requestBuilder = builder ?? new CometChat.ReactionsRequestBuilder().setLimit(DEFAULT_LIMIT); requestBuilder.setMessageId(messageId); this.request = requestBuilder.build(); } catch { this.request = null; } } /** Fetch the next page of reactions. Returns empty array when exhausted. */ fetchNext() { if (!this.request) return Promise.resolve([]); return this.request.fetchNext(); } }; // src/components/CometChatReactionList/CometChatReactionList.reducer.ts function groupByEmoji(reactions) { const map = /* @__PURE__ */ new Map(); for (const reaction of reactions) { const emoji = reaction.getReaction(); const existing = map.get(emoji) ?? []; map.set(emoji, [...existing, reaction]); } return map; } var initialReactionListState = { allReactions: [], groupedReactions: /* @__PURE__ */ new Map(), selectedEmoji: null, fetchState: "idle", hasMore: true, isFetching: false }; function reactionListReducer(state, action) { switch (action.type) { case "FETCH_START": return { ...state, isFetching: true, // Only set fetchState to loading if we have no data yet fetchState: state.allReactions.length === 0 ? "loading" : state.fetchState }; case "FETCH_SUCCESS": { const merged = [...state.allReactions, ...action.reactions]; const grouped = groupByEmoji(merged); return { ...state, allReactions: merged, groupedReactions: grouped, fetchState: merged.length === 0 ? "empty" : "loaded", hasMore: action.hasMore, isFetching: false }; } case "FETCH_ERROR": return { ...state, fetchState: "error", isFetching: false }; case "SELECT_EMOJI": return { ...state, selectedEmoji: action.emoji }; case "REMOVE_REACTION": { const filtered = state.allReactions.filter((r) => { const key = `${r.getReactedBy().getUid()}-${r.getReaction()}`; return key !== action.reactionId; }); const grouped = groupByEmoji(filtered); let selectedEmoji = state.selectedEmoji; if (selectedEmoji !== null && !grouped.has(selectedEmoji)) { selectedEmoji = null; } return { ...state, allReactions: filtered, groupedReactions: grouped, selectedEmoji, fetchState: filtered.length === 0 ? "empty" : state.fetchState }; } case "RESET": return initialReactionListState; default: return state; } } // src/components/CometChatReactionList/useCometChatReactionList.ts var DEFAULT_LIMIT2 = 20; function useCometChatReactionList(options) { const { message, reactionsRequestBuilder, onItemClick, onEmpty, onError } = options; const [state, dispatch] = useReducer(reactionListReducer, initialReactionListState); const managerRef = useRef(null); const fetchIdRef = useRef(""); const loggedInUserUidRef = useRef(""); const [loggedInUserUid, setLoggedInUserUid] = useStateRef(""); useEffect(() => { CometChat.getLoggedinUser().then((user) => { if (user) { loggedInUserUidRef.current = user.getUid(); setLoggedInUserUid(user.getUid()); } }).catch(() => { }); }, []); const handleError = useCallback( (error) => { onError?.(error); dispatch({ type: "FETCH_ERROR" }); }, [onError] ); const fetchMore = useCallback(async () => { if (!managerRef.current || !state.hasMore || state.isFetching) return; const currentFetchId = `fetch_${String(Date.now())}`; fetchIdRef.current = currentFetchId; dispatch({ type: "FETCH_START" }); try { const reactions = await managerRef.current.fetchNext(); if (fetchIdRef.current !== currentFetchId) return; dispatch({ type: "FETCH_SUCCESS", reactions, hasMore: reactions.length >= DEFAULT_LIMIT2 }); } catch (error) { if (fetchIdRef.current !== currentFetchId) return; handleError(error); } }, [state.hasMore, state.isFetching, handleError]); useEffect(() => { const messageId = message.getId(); if (!messageId) return; managerRef.current = new CometChatReactionListManager(messageId, reactionsRequestBuilder); dispatch({ type: "RESET" }); const currentFetchId = `fetch_${String(Date.now())}`; fetchIdRef.current = currentFetchId; dispatch({ type: "FETCH_START" }); managerRef.current.fetchNext().then((reactions) => { if (fetchIdRef.current !== currentFetchId) return; dispatch({ type: "FETCH_SUCCESS", reactions, hasMore: reactions.length >= DEFAULT_LIMIT2 }); }).catch((error) => { if (fetchIdRef.current !== currentFetchId) return; handleError(error); }); }, [message.getId(), reactionsRequestBuilder]); const selectEmoji = useCallback((emoji) => { dispatch({ type: "SELECT_EMOJI", emoji }); }, []); const isCurrentUser = useCallback( (reaction) => { const uid = loggedInUserUidRef.current || loggedInUserUid; return reaction.getReactedBy().getUid() === uid; }, [loggedInUserUid] ); const handleItemClick = useCallback( (reaction) => { if (!isCurrentUser(reaction)) return; const reactionId = `${reaction.getReactedBy().getUid()}-${reaction.getReaction()}`; dispatch({ type: "REMOVE_REACTION", reactionId }); onItemClick?.(reaction, message); const remainingCount = state.allReactions.filter((r) => { const key = `${r.getReactedBy().getUid()}-${r.getReaction()}`; return key !== reactionId; }).length; if (remainingCount === 0) { onEmpty?.(); } }, [isCurrentUser, onItemClick, message, onEmpty, state.allReactions] ); const retry = useCallback(() => { const messageId = message.getId(); if (!messageId) return; managerRef.current = new CometChatReactionListManager(messageId, reactionsRequestBuilder); dispatch({ type: "RESET" }); const currentFetchId = `fetch_${String(Date.now())}`; fetchIdRef.current = currentFetchId; dispatch({ type: "FETCH_START" }); managerRef.current.fetchNext().then((reactions) => { if (fetchIdRef.current !== currentFetchId) return; dispatch({ type: "FETCH_SUCCESS", reactions, hasMore: reactions.length >= DEFAULT_LIMIT2 }); }).catch((error) => { if (fetchIdRef.current !== currentFetchId) return; handleError(error); }); }, [message, reactionsRequestBuilder, handleError]); const emojiTabs = useMemo( () => Array.from(state.groupedReactions.keys()), [state.groupedReactions] ); const totalCount = state.allReactions.length; const filteredReactions = useMemo(() => { if (state.selectedEmoji === null) return state.allReactions; return state.groupedReactions.get(state.selectedEmoji) ?? []; }, [state.allReactions, state.groupedReactions, state.selectedEmoji]); return { message, allReactions: state.allReactions, groupedReactions: state.groupedReactions, selectedEmoji: state.selectedEmoji, fetchState: state.fetchState, hasMore: state.hasMore, isFetching: state.isFetching, emojiTabs, totalCount, filteredReactions, selectEmoji, fetchMore, handleItemClick, isCurrentUser, retry, loggedInUserUid: loggedInUserUidRef.current || loggedInUserUid }; } function useStateRef(initial) { const [value, setValue] = useReducer((_, next) => next, initial); return [value, setValue]; } var CometChatReactionListTabs = ({ className }) => { const { selectedEmoji, emojiTabs, totalCount, groupedReactions, selectEmoji } = useCometChatReactionListContext(); const { getLocalizedString } = useLocale(); const tabsContainerRef = useRef(null); const allText = getLocalizedString("reaction_list_all") || "All"; const tabs = useMemo( () => [ { id: null, label: allText, count: totalCount }, ...emojiTabs.map((emoji) => ({ id: emoji, label: emoji, count: groupedReactions.get(emoji)?.length ?? 0 })) ], [allText, totalCount, emojiTabs, groupedReactions] ); const handleTabKeyDown = useCallback( (e, tabIndex) => { let nextIndex = tabIndex; if (e.key === "ArrowRight") { nextIndex = (tabIndex + 1) % tabs.length; e.preventDefault(); } else if (e.key === "ArrowLeft") { nextIndex = (tabIndex - 1 + tabs.length) % tabs.length; e.preventDefault(); } else if (e.key === "Home") { nextIndex = 0; e.preventDefault(); } else if (e.key === "End") { nextIndex = tabs.length - 1; e.preventDefault(); } else { return; } const nextTab = tabs[nextIndex]; if (nextTab) { selectEmoji(nextTab.id); if (tabsContainerRef.current) { const tabElements = tabsContainerRef.current.querySelectorAll('[role="tab"]'); tabElements[nextIndex]?.focus(); } } }, [tabs, selectEmoji] ); const rootClass = ["cometchat-reaction-list__tabs", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsx( "div", { ref: tabsContainerRef, className: rootClass, role: "tablist", "aria-label": getLocalizedString("accessibility_reaction_filters"), children: tabs.map((tab, index) => { const isActive = selectedEmoji === tab.id; const tabClass = [ "cometchat-reaction-list__tabs-tab", isActive ? "cometchat-reaction-list__tabs-tab--active" : "" ].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs( "button", { type: "button", role: "tab", "aria-selected": isActive, tabIndex: isActive ? 0 : -1, className: tabClass, onClick: () => { selectEmoji(tab.id); }, onKeyDown: (e) => { handleTabKeyDown(e, index); }, children: [ /* @__PURE__ */ jsx( "span", { className: [ "cometchat-reaction-list__tabs-tab-emoji", isActive ? "cometchat-reaction-list__tabs-tab-emoji--active" : "" ].filter(Boolean).join(" "), children: tab.label } ), /* @__PURE__ */ jsx( "span", { className: [ "cometchat-reaction-list__tabs-tab-count", isActive ? "cometchat-reaction-list__tabs-tab-count--active" : "" ].filter(Boolean).join(" "), children: tab.count } ) ] }, tab.id ?? "all" ); }) } ); }; var CometChatReactionListItems = ({ className }) => { const { filteredReactions, fetchState, hasMore, isFetching, fetchMore, handleItemClick, isCurrentUser, selectedEmoji } = useCometChatReactionListContext(); const { getLocalizedString } = useLocale(); const sentinelRef = useRef(null); const youText = getLocalizedString("reaction_list_you") || "You"; const clickToRemoveText = getLocalizedString("reaction_list_click_to_remove") || "Click to remove"; useEffect(() => { const sentinel = sentinelRef.current; if (!sentinel || !hasMore || isFetching) return; const observer = new IntersectionObserver( (entries) => { const entry = entries[0]; if (entry.isIntersecting) { void fetchMore(); } }, { rootMargin: "50px" } ); observer.observe(sentinel); return () => { observer.disconnect(); }; }, [hasMore, isFetching, fetchMore]); const handleKeyDown = useCallback( (e, reaction) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleItemClick(reaction); } }, [handleItemClick] ); if (fetchState === "loading" || fetchState === "error" || fetchState === "empty") return null; const rootClass = ["cometchat-reaction-list__list", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs( "div", { className: rootClass, role: "list", "aria-label": selectedEmoji ? `Users who reacted with ${selectedEmoji}` : "All users who reacted", children: [ filteredReactions.map((reaction) => { const reactedBy = reaction.getReactedBy(); const uid = reactedBy.getUid(); const name = reactedBy.getName(); const avatar = reactedBy.getAvatar(); const emoji = reaction.getReaction(); const isMine = isCurrentUser(reaction); const displayName = isMine ? youText : name; const itemClass = [ "cometchat-reaction-list__list-item", isMine ? "cometchat-reaction-list__list-item--current-user" : "cometchat-reaction-list__list-item--readonly" ].filter(Boolean).join(" "); const ariaLabel = isMine ? `${displayName} reacted with ${emoji}. ${clickToRemoveText}` : `${name} reacted with ${emoji}`; return /* @__PURE__ */ jsxs( "div", { className: itemClass, role: "listitem", tabIndex: isMine ? 0 : -1, "aria-label": ariaLabel, onClick: isMine ? () => { handleItemClick(reaction); } : void 0, onKeyDown: isMine ? (e) => { handleKeyDown(e, reaction); } : void 0, children: [ /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__item-avatar", children: /* @__PURE__ */ jsxs(CometChatAvatar.Root, { name, image: avatar, children: [ /* @__PURE__ */ jsx(CometChatAvatar.Image, {}), /* @__PURE__ */ jsx(CometChatAvatar.Initials, {}) ] }) }), /* @__PURE__ */ jsxs("div", { className: "cometchat-reaction-list__item-info", children: [ /* @__PURE__ */ jsx("span", { className: "cometchat-reaction-list__item-name", children: displayName }), isMine && /* @__PURE__ */ jsx("span", { className: "cometchat-reaction-list__item-hint", children: clickToRemoveText }) ] }), /* @__PURE__ */ jsx("span", { className: "cometchat-reaction-list__item-emoji", "aria-hidden": "true", children: emoji }) ] }, `${uid}-${emoji}` ); }), isFetching && filteredReactions.length > 0 && /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__loading-more", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__spinner" }) }), hasMore && /* @__PURE__ */ jsx("div", { ref: sentinelRef, "aria-hidden": "true", style: { height: 1 } }) ] } ); }; var SHIMMER_ITEM_COUNT = 4; var CometChatReactionListLoadingState = ({ className }) => { const { getLocalizedString } = useLocale(); const { fetchState } = useCometChatReactionListContext(); if (fetchState !== "loading") return null; const rootClass = ["cometchat-reaction-list__shimmer", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsx( "div", { className: rootClass, "aria-busy": "true", "aria-label": getLocalizedString("accessibility_loading_reactions"), children: Array.from({ length: SHIMMER_ITEM_COUNT }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "cometchat-reaction-list__shimmer-item", children: [ /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__shimmer-item-icon" }), /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__shimmer-item-content" }), /* @__PURE__ */ jsx("div", { className: "cometchat-reaction-list__shimmer-item-tailview" }) ] }, i)) } ); }; var CometChatReactionListErrorState = ({ className }) => { const { fetchState, retry } = useCometChatReactionListContext(); const { getLocalizedString } = useLocale(); if (fetchState !== "error") return null; const errorText = getLocalizedString("reaction_list_error") || "Something went wrong"; const retryText = getLocalizedString("reaction_list_retry") || "Retry"; const rootClass = ["cometchat-reaction-list__error", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs("div", { className: rootClass, role: "alert", children: [ /* @__PURE__ */ jsx("p", { className: "cometchat-reaction-list__error-text", children: errorText }), /* @__PURE__ */ jsx("button", { type: "button", className: "cometchat-reaction-list__retry-button", onClick: retry, children: retryText }) ] }); }; var CometChatReactionListEmptyState = ({ className }) => { const { fetchState } = useCometChatReactionListContext(); const { getLocalizedString } = useLocale(); if (fetchState !== "empty") return null; const emptyText = getLocalizedString("reaction_list_empty") || "No reactions yet"; const rootClass = ["cometchat-reaction-list__empty", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsx("div", { className: rootClass, role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "cometchat-reaction-list__empty-text", children: emptyText }) }); }; var DefaultLayout = () => { return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(CometChatReactionListTabs, {}), /* @__PURE__ */ jsx(CometChatReactionListLoadingState, {}), /* @__PURE__ */ jsx(CometChatReactionListErrorState, {}), /* @__PURE__ */ jsx(CometChatReactionListEmptyState, {}), /* @__PURE__ */ jsx(CometChatReactionListItems, {}) ] }); }; var CometChatReactionListRoot = ({ message, reactionsRequestBuilder, onItemClick, onEmpty, onError, children, className }) => { const hookOptions = { message }; if (reactionsRequestBuilder !== void 0) hookOptions.reactionsRequestBuilder = reactionsRequestBuilder; if (onItemClick !== void 0) hookOptions.onItemClick = onItemClick; if (onEmpty !== void 0) hookOptions.onEmpty = onEmpty; if (onError !== void 0) hookOptions.onError = onError; const { getLocalizedString } = useLocale(); const hookReturn = useCometChatReactionList(hookOptions); const contextValue = useMemo( () => ({ ...hookReturn, message }), [hookReturn, message] ); const hasChildren = React.Children.count(children) > 0; const rootClass = ["cometchat-reaction-list", className ?? ""].filter(Boolean).join(" "); return /* @__PURE__ */ jsx(CometChatReactionListContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx( "div", { className: rootClass, role: "dialog", "aria-label": getLocalizedString("accessibility_reaction_list"), "aria-modal": "false", children: hasChildren ? children : /* @__PURE__ */ jsx(DefaultLayout, {}) } ) }); }; var CometChatReactionListComponent = (props) => { return /* @__PURE__ */ jsx(CometChatReactionListRoot, { ...props }); }; var CometChatReactionList = Object.assign(CometChatReactionListComponent, { Root: CometChatReactionListRoot, Tabs: CometChatReactionListTabs, Items: CometChatReactionListItems, LoadingState: CometChatReactionListLoadingState, ErrorState: CometChatReactionListErrorState, EmptyState: CometChatReactionListEmptyState }); var CHIP_WIDTH = 48; var REACTIONS_PADDING = 8; var ReactionListPopoverContent = () => { const { message, reactionsRequestBuilder, onReactionClick } = useCometChatReactionsContext(); const { close } = useCometChatPopoverContext(); const handleItemClick = useCallback( (reaction) => { onReactionClick(reaction.getReaction()); }, [onReactionClick] ); const handleEmpty = useCallback(() => { close(); }, [close]); return /* @__PURE__ */ jsx( CometChatReactionList.Root, { message, reactionsRequestBuilder, onItemClick: handleItemClick, onEmpty: handleEmpty } ); }; var CometChatReactionsBar = ({ maxVisible: maxVisibleProp, className }) => { const { getLocalizedString } = useLocale(); const { reactions, alignment } = useCometChatReactionsContext(); const barRef = useRef(null); const [computedMaxVisible, setComputedMaxVisible] = useState( maxVisibleProp ?? reactions.length // Show all initially; ResizeObserver constrains after layout ); const maxVisible = maxVisibleProp ?? computedMaxVisible; useEffect(() => { if (maxVisibleProp != null || !barRef.current) return; const footerView = barRef.current.closest('[class*="body-footer-view"]'); const body = footerView?.parentElement; const contentView = body?.querySelector('[class*="body-content-view"]'); if (!contentView) { setComputedMaxVisible(100); return; } const observer = new ResizeObserver((entries) => { for (const entry of entries) { const width = entry.contentRect.width - REACTIONS_PADDING; const calculated = Math.max(1, Math.floor(width / CHIP_WIDTH)); setComputedMaxVisible(calculated); } }); observer.observe(contentView); return () => { observer.disconnect(); }; }, [maxVisibleProp]); const totalReactions = reactions.length; const showOverflow = totalReactions > maxVisible && maxVisible > 2; const visibleCount = showOverflow ? maxVisible - 1 : Math.min(totalReactions, maxVisible); const visibleReactions = reactions.slice(0, visibleCount); const overflowCount = totalReactions - visibleCount; const handleKeyDown = useCallback((e) => { const target = e.target; if (!barRef.current) return; const buttons = barRef.current.querySelectorAll("button"); if (!buttons.length) return; const currentIndex = Array.from(buttons).indexOf(target); if (currentIndex === -1) return; let nextIndex = currentIndex; if (e.key === "ArrowRight") { nextIndex = (currentIndex + 1) % buttons.length; e.preventDefault(); } else if (e.key === "ArrowLeft") { nextIndex = (currentIndex - 1 + buttons.length) % buttons.length; e.preventDefault(); } else { return; } const nextButton = buttons[nextIndex]; if (nextButton) { nextButton.focus(); } }, []); const barClass = ["cometchat-reactions__bar", className ?? ""].filter(Boolean).join(" "); const listPlacement = alignment === "left" ? "right" : "left"; if (reactions.length === 0) return null; return /* @__PURE__ */ jsxs( "div", { ref: barRef, className: barClass, role: "group", "aria-label": getLocalizedString("accessibility_reactions"), "aria-live": "polite", onKeyDown: handleKeyDown, children: [ visibleReactions.map((reaction) => /* @__PURE__ */ jsx("div", { className: "cometchat-reactions__info-wrapper", children: /* @__PURE__ */ jsx( CometChatPopover, { showOnHover: true, debounceOnHover: 500, placement: "top", trigger: /* @__PURE__ */ jsx(CometChatReactionsChip, { reaction }), content: /* @__PURE__ */ jsx(CometChatReactionsInfo, { emoji: reaction.getReaction() }) } ) }, reaction.getReaction())), overflowCount > 0 && /* @__PURE__ */ jsx( CometChatPopover, { placement: listPlacement, closeOnOutsideClick: true, trigger: /* @__PURE__ */ jsx(CometChatReactionsOverflow, { count: overflowCount }), content: /* @__PURE__ */ jsx(ReactionListPopoverContent, {}) } ) ] } ); }; var CometChatReactionsRoot = ({ message, alignment = "left", reactionsRequestBuilder, onReactionClick, hoverDebounceTime = 500, onError, children, className }) => { const reactions = useMemo(() => message.getReactions(), [message]); const maxVisible = reactions.length; const visibleReactions = reactions; const overflowCount = 0; const handleReactionClick = useCallback( (emoji) => { onReactionClick?.(emoji, message); }, [onReactionClick, message] ); const contextValue = useMemo( () => ({ message, reactions, alignment, maxVisible, visibleReactions, overflowCount, onReactionClick: handleReactionClick, reactionsRequestBuilder, hoverDebounceTime, onError }), [ message, reactions, alignment, maxVisible, visibleReactions, overflowCount, handleReactionClick, reactionsRequestBuilder, hoverDebounceTime, onError ] ); const rootClass = ["cometchat-reactions", className ?? ""].filter(Boolean).join(" "); if (reactions.length === 0) return null; return /* @__PURE__ */ jsx(CometChatReactionsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { className: rootClass, children: children ?? /* @__PURE__ */ jsx(CometChatReactionsBar, {}) }) }); }; export { CometChatReactionList, CometChatReactionsBar, CometChatReactionsChip, CometChatReactionsInfo, CometChatReactionsOverflow, CometChatReactionsRoot, useCometChatReactionList, useCometChatReactionListContext, useCometChatReactionsContext };