Skip to content

Commit

Permalink
CW-instant-reorder
Browse files Browse the repository at this point in the history
Added feedItem keyboard support
  • Loading branch information
MeyerPV committed Nov 16, 2024
1 parent cc29ff2 commit 9f64758
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 9 deletions.
15 changes: 15 additions & 0 deletions src/pages/common/components/ChatComponent/ChatComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import {
} from "./utils";
import styles from "./ChatComponent.module.scss";
import { BaseTextEditorHandles } from "@/shared/ui-kit/TextEditor/BaseTextEditor";
import { useFeedItemContext } from "../FeedItem";

const BASE_CHAT_INPUT_HEIGHT = 48;
const BASE_ORDER_INTERVAL = 1000;
Expand Down Expand Up @@ -258,6 +259,20 @@ export default function ChatComponent({
parseStringToTextEditorValue(),
);

const {
setIsInputFocused
} = useFeedItemContext();

useEffect(() => {
const isEmpty = checkIsTextEditorValueEmpty(message);
if(!isEmpty || message.length > 1) {
setIsInputFocused?.(true);
} else {
setIsInputFocused?.(false);
}

},[message, setIsInputFocused])

const emojiCount = useMemo(
() => countTextEditorEmojiElements(message),
[message],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,6 @@ function DiscussionFeedCard(props, ref) {
const cardTitle = discussion?.title;
const commonNotion = outerCommonNotion ?? common?.notion;

// const ownerId = useMemo(() => {
// if(item.userId) {
// return item.userId
// }
// },[item.userId])

const handleOpenChat = useCallback(() => {
if (discussion && !isPreviewMode) {
setChatItem({
Expand Down
2 changes: 1 addition & 1 deletion src/pages/common/components/FeedCard/FeedCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ const FeedCard = (props, ref) => {
]);

return (
<div ref={containerRef} >
<div ref={containerRef}>
{!isPreviewMode && <div className={styles.toggleCard} {...getToggleProps()}>{feedItemBaseContent}</div>}
<div {...getCollapseProps()}>
<CommonCard
Expand Down
1 change: 1 addition & 0 deletions src/pages/common/components/FeedItem/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface GetLastMessageOptions {
}

export interface FeedItemContextValue {
setIsInputFocused?: (isFocused: boolean) => void;
setExpandedFeedItemId?: (feedItemId: string | null) => void;
renderFeedItemBaseContent?: (props: FeedItemBaseContentProps) => ReactNode;
onFeedItemUpdate?: (item: CommonFeed, isRemoved: boolean) => void;
Expand Down
53 changes: 51 additions & 2 deletions src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import React, {
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import PullToRefresh from "react-simple-pull-to-refresh";
import { useDeepCompareEffect, useWindowSize } from "react-use";
import { useDeepCompareEffect, useKey, useWindowSize } from "react-use";
import classNames from "classnames";
import { selectUser } from "@/pages/Auth/store/selectors";
import { useCommonMember } from "@/pages/OldCommon/hooks";
Expand Down Expand Up @@ -44,7 +44,7 @@ import {
ROUTE_PATHS,
} from "@/shared/constants";
import { useRoutesContext } from "@/shared/contexts";
import { useMemoizedFunction, useQueryParams } from "@/shared/hooks";
import { useElementPresence, useMemoizedFunction, useQueryParams } from "@/shared/hooks";
import { useGovernanceByCommonId } from "@/shared/hooks/useCases";
import { useDisableOverscroll } from "@/shared/hooks/useDisableOverscroll";
import { useIsTabletView } from "@/shared/hooks/viewport";
Expand Down Expand Up @@ -90,6 +90,7 @@ import {
import {
BATCHES_AMOUNT_TO_PRELOAD,
ITEMS_AMOUNT_TO_PRE_LOAD_MESSAGES,
MENTION_TAG_ELEMENT,
} from "./constants";
import { useUserForProfile } from "./hooks";
import {
Expand Down Expand Up @@ -353,6 +354,8 @@ const FeedLayout: ForwardRefRenderFunction<FeedLayoutRef, FeedLayoutProps> = (
const activeFeedItemId = chatItem?.feedItemId || feedItemIdForAutoChatOpen;
const sizeKey = `${windowWidth}_${contentWidth}`;

const activeFeedItemIndex = useMemo(() => allFeedItems.findIndex((item) => item.itemId === activeFeedItemId), [activeFeedItemId, allFeedItems]);

const getUserCircleIds = useCallback(
(commonId) => {
return Object.values(
Expand Down Expand Up @@ -405,6 +408,50 @@ const FeedLayout: ForwardRefRenderFunction<FeedLayoutRef, FeedLayoutProps> = (
setChatItem(nextChatItem);
}, []);

const isMentionOpen = useElementPresence(MENTION_TAG_ELEMENT.key, MENTION_TAG_ELEMENT.value);
const [isInputFocused, setIsInputFocused] = useState(false);

const handleArrowUp = (event, activeFeedItemIndex, allFeedItems, isMentionOpen, setChatItem) => {
if (!isMentionOpen && !isInputFocused) {
event.preventDefault();
event.stopPropagation();

if (activeFeedItemIndex > 0) {
const nextFeedItemId = allFeedItems[activeFeedItemIndex - 1]?.itemId;

setChatItem({ feedItemId: nextFeedItemId });
}
}
};

const handleArrowDown = (event, activeFeedItemIndex, allFeedItems, isMentionOpen, setChatItem) => {
if (!isMentionOpen && !isInputFocused) {
event.preventDefault();
event.stopPropagation();

if (activeFeedItemIndex < allFeedItems.length - 1) {
const nextFeedItemId = allFeedItems[activeFeedItemIndex + 1]?.itemId;

setChatItem({ feedItemId: nextFeedItemId });
}
}
};

// Inside your component
useKey(
"ArrowUp",
(event) => handleArrowUp(event, activeFeedItemIndex, allFeedItems, isMentionOpen, setChatItem),
{},
[activeFeedItemIndex, allFeedItems, isMentionOpen, isInputFocused]
);

useKey(
"ArrowDown",
(event) => handleArrowDown(event, activeFeedItemIndex, allFeedItems, isMentionOpen, setChatItem),
{},
[activeFeedItemIndex, allFeedItems, isMentionOpen, isInputFocused]
);

const chatContextValue = useMemo<ChatContextValue>(
() => ({
setChatItem: setActiveChatItem,
Expand Down Expand Up @@ -644,6 +691,7 @@ const FeedLayout: ForwardRefRenderFunction<FeedLayoutRef, FeedLayoutProps> = (
// so we will not have extra re-renders of ALL rendered items
const feedItemContextValue = useMemo<FeedItemContextValue>(
() => ({
setIsInputFocused,
setExpandedFeedItemId,
renderFeedItemBaseContent,
onFeedItemUpdate,
Expand All @@ -656,6 +704,7 @@ const FeedLayout: ForwardRefRenderFunction<FeedLayoutRef, FeedLayoutProps> = (
onActiveItemDataChange: handleActiveFeedItemDataChange,
}),
[
setIsInputFocused,
renderFeedItemBaseContent,
onFeedItemUpdate,
onFeedItemUnfollowed,
Expand Down
4 changes: 4 additions & 0 deletions src/pages/commonFeed/components/FeedLayout/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export const BATCHES_AMOUNT_TO_PRELOAD = 2;
export const ITEMS_AMOUNT_TO_PRE_LOAD_MESSAGES = 10;
export const MENTION_TAG_ELEMENT = {
key: "data-cy",
value: "mentions-portal"
}
2 changes: 2 additions & 0 deletions src/shared/hooks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ export { default as useImageSizeCheck } from "./useImageSizeCheck";
export { default as useLightThemeOnly } from "./useLightThemeOnly";
export * from "./useToggle";
export * from "./useTraceUpdate";
export * from "./useElementPresence";
export * from "./useIsElementFocused";
48 changes: 48 additions & 0 deletions src/shared/hooks/useElementPresence.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect, useState } from "react";

export const useElementPresence = (attribute, value) => {
const [elementPresent, setElementPresent] = useState(false);

useEffect(() => {
const observerCallback = (mutationsList) => {
mutationsList.forEach((mutation) => {
// Check for added nodes
mutation.addedNodes.forEach((node) => {
if (
node.nodeType === 1 && // Ensure it's an element
node.getAttribute(attribute) === value
) {
setElementPresent(true);
}
});

// Check for removed nodes
mutation.removedNodes.forEach((node) => {
if (
node.nodeType === 1 && // Ensure it's an element
node.getAttribute(attribute) === value
) {
setElementPresent(false);
}
});
});
};

const observer = new MutationObserver(observerCallback);

// Start observing the entire document
observer.observe(document.body, { childList: true, subtree: true });

// Check initially if the element is already present
const initialElement = document.querySelector(`[${attribute}="${value}"]`);
if (initialElement) {
setElementPresent(true);
}

return () => {
observer.disconnect(); // Cleanup observer on unmount
};
}, [attribute, value]);

return elementPresent;
};
24 changes: 24 additions & 0 deletions src/shared/hooks/useIsElementFocused.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useState } from "react";

export const useIsElementFocused = (id: string) => {
const [isFocused, setIsFocused] = useState(false);

useEffect(() => {
const handleFocusChange = () => {
const element = document.getElementById(id);
setIsFocused(document.activeElement === element);
};

// Listen to focus changes globally
document.addEventListener("focusin", handleFocusChange);
document.addEventListener("focusout", handleFocusChange);

// Cleanup
return () => {
document.removeEventListener("focusin", handleFocusChange);
document.removeEventListener("focusout", handleFocusChange);
};
}, [id]);

return isFocused;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const countTextEditorEmojiElements = (

let hasText = false;
let emojiCount = 0;

editorValue.forEach((element) => {
if (
(element as ParagraphElement)?.type === ElementType.Paragraph &&
Expand Down

0 comments on commit 9f64758

Please sign in to comment.