PromucFlow_constructor/app/client/src/components/ads/MentionsInput.tsx
2021-08-06 14:47:02 +05:30

257 lines
6.7 KiB
TypeScript

import React, { useCallback, useMemo, useRef, useState } from "react";
import { DraftHandleValue, EditorState } from "draft-js";
import Editor from "@draft-js-plugins/editor";
import ProfileImage, { Profile } from "pages/common/ProfileImage";
import createMentionPlugin, { MentionData } from "@draft-js-plugins/mention";
import styled from "styled-components";
import "@draft-js-plugins/mention/lib/plugin.css";
import "draft-js/dist/Draft.css";
import { getTypographyByKey } from "constants/DefaultTheme";
import { EntryComponentProps } from "@draft-js-plugins/mention/lib/MentionSuggestions/Entry/Entry";
import Icon from "components/ads/Icon";
import { INVITE_A_NEW_USER, createMessage } from "constants/messages";
import { USER_PHOTO_URL } from "constants/userConstants";
import scrollIntoView from "scroll-into-view-if-needed";
import { useEffect } from "react";
const StyledMention = styled.span`
color: ${(props) => props.theme.colors.comments.mention};
`;
export function MentionComponent(props: {
children: React.ReactNode;
entityKey: string;
}) {
const { children } = props;
return <StyledMention>@{children}</StyledMention>;
}
export enum Trigger {
"@" = "@",
"+" = "+",
}
const StyledSuggestionsComponent = styled.div<{ isFocused?: boolean }>`
display: flex;
width: 250px;
padding: ${(props) =>
`${props.theme.spaces[4]}px ${props.theme.spaces[6]}px`};
& ${Profile} {
margin-right: ${(props) => props.theme.spaces[4]}px;
}
align-items: center;
&:hover {
background-color: ${(props) => props.theme.colors.mentionSuggestion.hover};
}
${(props) =>
props.isFocused
? `
background-color: ${props.theme.colors.mentionSuggestion.hover};
`
: ""}
`;
const Name = styled.div`
${(props) => getTypographyByKey(props, "h5")}
color: ${(props) => props.theme.colors.mentionSuggestion.nameText};
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
`;
const Username = styled.div`
${(props) => getTypographyByKey(props, "p3")}
color: ${(props) => props.theme.colors.mentionSuggestion.usernameText};
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
`;
const PlusCircle = styled.div`
width: 25px;
height: 25px;
display: flex;
border-radius: 50%;
align-items: center;
justify-content: center;
background-color: ${(props) =>
props.theme.colors.mentionsInput.mentionsInviteBtnPlusIcon};
& svg path {
stroke: #fff;
}
margin-right: ${(props) => props.theme.spaces[4]}px;
flex-shrink: 0;
`;
const SuggestionRightContainer = styled.div`
flex: 1;
min-width: 0;
`;
function SuggestionComponent(props: EntryComponentProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { theme, ...parentProps } = props;
const { user } = props.mention;
const mentionRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (parentProps.isFocused && mentionRef.current) {
scrollIntoView(mentionRef.current, {
scrollMode: "if-needed",
behavior: "smooth",
block: "nearest",
inline: "nearest",
});
}
}, [parentProps.isFocused]);
if (props.mention?.isInviteTrigger) {
return (
<StyledSuggestionsComponent ref={mentionRef} {...parentProps}>
<PlusCircle>
<Icon fillColor="#fff" name="plus" />
</PlusCircle>
<SuggestionRightContainer>
<Name>{createMessage(INVITE_A_NEW_USER)}</Name>
<Username>{props.mention.name}</Username>
</SuggestionRightContainer>
</StyledSuggestionsComponent>
);
}
return (
<StyledSuggestionsComponent ref={mentionRef} {...parentProps}>
<div style={{ flexShrink: 0 }}>
<ProfileImage
side={25}
source={`/api/${USER_PHOTO_URL}/${user?.username}`}
userName={user?.username || ""}
/>
</div>
<SuggestionRightContainer>
<Name>{props.mention.name}</Name>
<Username>{user?.username}</Username>
</SuggestionRightContainer>
</StyledSuggestionsComponent>
);
}
const StyledContainer = styled.div`
overflow: auto;
flex: 1;
.mentions-list {
position: absolute;
background: #fff;
border-radius: 2px;
box-shadow: 0px 4px 30px 0px rgb(220 220 220);
cursor: pointer;
display: flex;
flex-direction: column;
overflow: auto;
max-height: 300px;
z-index: 2;
}
`;
type Props = {
suggestions: Array<MentionData>;
onSubmit: (editorState: EditorState) => void;
editorState: EditorState;
setEditorState: (editorState: EditorState) => void;
readOnly?: boolean;
onSearchSuggestions: (props: { value: string; trigger: string }) => void;
autoFocus: boolean;
placeholder?: string;
onAddMention?: (mention: MentionData) => void;
};
function MentionsInput({
autoFocus,
editorState,
onAddMention,
onSearchSuggestions,
onSubmit,
placeholder,
setEditorState,
suggestions,
}: Props) {
const ref = useRef<Editor | null>(null);
const [open, setOpen] = useState(false);
const { MentionSuggestions, plugins } = useMemo(() => {
const mentionPlugin = createMentionPlugin({
mentionRegExp: "[\\w@\\._]",
mentionTrigger: ["@", "+"],
mentionComponent: MentionComponent,
theme: {
mentionSuggestions: "mentions-list",
},
});
const { MentionSuggestions } = mentionPlugin;
return { plugins: [mentionPlugin], MentionSuggestions };
}, []);
const onOpenChange = useCallback((_open: boolean) => {
setOpen(_open);
}, []);
const handleReturn = useCallback(
(e: React.KeyboardEvent, editorState: EditorState): DraftHandleValue => {
if (!e.nativeEvent.shiftKey && !open) {
onSubmit(editorState);
return "handled";
}
return "not-handled";
},
[open],
);
const focusInput = useCallback(() => {
// Forcing focus breaks plugins
// Ref: https://github.com/draft-js-plugins/draft-js-plugins/issues/800
setTimeout(() => ref.current?.focus());
}, []);
const setRef = useCallback((editorRef) => {
ref.current = editorRef;
if (autoFocus) focusInput();
}, []);
return (
<StyledContainer onClick={focusInput}>
<Editor
editorKey={"editor"}
editorState={editorState}
handleReturn={handleReturn}
onChange={setEditorState}
placeholder={placeholder}
plugins={plugins}
ref={setRef}
webDriverTestID="mentions-input"
/>
<MentionSuggestions
entryComponent={SuggestionComponent}
onAddMention={(mention: MentionData) => {
if (onAddMention) {
onAddMention(mention);
}
}}
onOpenChange={onOpenChange}
onSearchChange={onSearchSuggestions}
open={open}
suggestions={suggestions}
/>
</StyledContainer>
);
}
export default MentionsInput;