Skip to content

Commit

Permalink
feat(chat): add usersQuery to context
Browse files Browse the repository at this point in the history
The users promise will be used to handle the mention functionality
directly in app.
  • Loading branch information
cannarocks committed Jan 17, 2024
1 parent dd01e47 commit d7f0040
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 80 deletions.
2 changes: 2 additions & 0 deletions src/stories/chat/_types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { BubbleMenuProps, EditorOptions } from "@tiptap/react";

type validationStatus = "success" | "warning" | "error";

export type SuggestedUser = { id: number; fullName: string; avatar: string };

export interface ChatEditorArgs extends Partial<EditorOptions> {
placeholderOptions?: Partial<PlaceholderOptions>;
hasInlineMenu?: boolean;
Expand Down
5 changes: 5 additions & 0 deletions src/stories/chat/context/chatContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Editor } from "@tiptap/react";
import React, { createContext, useContext, useMemo, useState } from "react";
import { SuggestedUser } from "../_types";

export type ChatContextType = {
isEditing: boolean;
Expand All @@ -9,16 +10,19 @@ export type ChatContextType = {
triggerSave: () => void;
editor?: Editor;
setEditor: React.Dispatch<React.SetStateAction<Editor | undefined>>;
mentionableUsers: (props: { query: string }) => Promise<SuggestedUser[]>;
};

export const ChatContext = createContext<ChatContextType | null>(null);

export const ChatContextProvider = ({
onSave,
setMentionableUsers,
children,
}: {
onSave?: (editor: Editor) => void;
children: React.ReactNode;
setMentionableUsers: (props: { query: string }) => Promise<SuggestedUser[]>;
}) => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [comment, setComment] = useState<string>("");
Expand All @@ -38,6 +42,7 @@ export const ChatContextProvider = ({
editor.commands.clearContent();
}
},
mentionableUsers: setMentionableUsers,
}),
[comment, setComment, isEditing, setIsEditing, editor, setEditor, onSave]
);
Expand Down
67 changes: 65 additions & 2 deletions src/stories/chat/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Button } from "../buttons/button";
import { Col } from "../grid/col";
import { Grid } from "../grid/grid";
import { Row } from "../grid/row";
import { ChatEditorArgs } from "./_types";
import { ChatEditorArgs, SuggestedUser } from "./_types";
import { Comment } from "./parts/comment";

interface EditorStoryArgs extends ChatEditorArgs {
Expand Down Expand Up @@ -47,11 +47,64 @@ const ChatPanel = ({ background, ...args }: EditorStoryArgs) => {
};

const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
const getUsers = async ({ query }: { query: string }) => {
return [
{
id: 1,
fullName: "John Doe",
avatar: "https://i.pravatar.cc/150?img=1",
},
{
id: 2,
fullName: "Jane Doe",
avatar: "https://i.pravatar.cc/150?img=2",
},
{
id: 3,
fullName: "John Smith",
avatar: "https://i.pravatar.cc/150?img=3",
},
{
id: 4,
fullName: "Jane Smith",
avatar: "https://i.pravatar.cc/150?img=4",
},
{
id: 5,
fullName: "Pippo Baudo",
avatar: "https://i.pravatar.cc/150?img=5",
},
{
id: 6,
fullName: "Pippo Franco",
avatar: "https://i.pravatar.cc/150?img=6",
},
{
id: 7,
fullName: "Pippo Inzaghi",
avatar: "https://i.pravatar.cc/150?img=7",
},
{
id: 8,
fullName: "Pippo Civati",
avatar: "https://i.pravatar.cc/150?img=8",
},
{
id: 9,
fullName: "Pippo Delbono",
avatar: "https://i.pravatar.cc/150?img=9",
},
].filter((item) => {
if (!query) return item;
return item.fullName.toLowerCase().startsWith(query.toLowerCase());
});
};

return (
<Grid>
<Row>
<Col xs={12} sm={8} md={6}>
<ChatProvider onSave={args.onSave}>
<ChatProvider setMentionableUsers={getUsers} onSave={args.onSave}>
<ChatPanel {...args} />
</ChatProvider>
</Col>
Expand All @@ -65,6 +118,16 @@ const defaultArgs: EditorStoryArgs = {
"<p>I'm <em>a</em> <strong>stupid</strong> <code>editor</code>!</p>",
onSave: (editor: TipTapEditor) => {
console.log("we have to save this", editor.getHTML());
const result: any[] = [];

editor.state.doc.descendants((node) => {
if (node.type.name === "mention") {
// Add only if it's not already in the array
if (!result.some((r) => r.id === node.attrs.id))
result.push(node.attrs);
}
});
console.log("mentions", result);
},
author: {
avatar: "LC",
Expand Down
5 changes: 4 additions & 1 deletion src/stories/chat/parts/bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const CommentBar = ({
<IconButton
onClick={() => {
editor.chain().focus();
editor.commands.insertContent("@");
const { from } = editor.state.selection;

const char = from > 1 ? " @" : "@";
editor.commands.insertContent(char);
}}
isPill={false}
>
Expand Down
7 changes: 6 additions & 1 deletion src/stories/chat/parts/comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { styled } from "styled-components";
import { Author } from "../_types";
import { Avatar } from "../../avatar";
import { CommentEditor } from "./editor";
import { useChatContext } from "../context/chatContext";

const CommentCard = styled(Card)`
padding: ${({ theme }) => `${theme.space.base * 3}px ${theme.space.sm}`};
Expand Down Expand Up @@ -52,6 +53,8 @@ export const Comment = ({
children,
date,
}: PropsWithChildren<{ author: Author; message: string; date: string }>) => {
const { mentionableUsers } = useChatContext();

return (
<CommentCard>
<AuthorContainer>
Expand All @@ -67,7 +70,9 @@ export const Comment = ({
<CommentDate>{date}</CommentDate>
</CommentTitle>
<ReadOnly>
<CommentEditor editable={false}>{message}</CommentEditor>
<CommentEditor mentionableUsers={mentionableUsers} editable={false}>
{message}
</CommentEditor>
</ReadOnly>
</div>
</AuthorContainer>
Expand Down
5 changes: 3 additions & 2 deletions src/stories/chat/parts/commentBox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from "styled-components";
import { ChatEditorArgs } from "../_types";
import { PropsWithChildren, useEffect, useState } from "react";
import { PropsWithChildren } from "react";
import { FloatingMenu } from "../../editor/floatingMenu";
import { Avatar } from "../../avatar";
import { useChatContext } from "../context/chatContext";
Expand Down Expand Up @@ -32,7 +32,7 @@ export const CommentBox = ({
}: PropsWithChildren<ChatEditorArgs>) => {
const { children, hasInlineMenu, hasButtonsMenu, bubbleOptions, author } =
props;
const { editor } = useChatContext();
const { editor, mentionableUsers } = useChatContext();

return (
<>
Expand All @@ -47,6 +47,7 @@ export const CommentBox = ({
</div>
<CommentEditor
placeholderOptions={placeholderOptions}
mentionableUsers={mentionableUsers}
children={children}
{...props}
/>
Expand Down
62 changes: 6 additions & 56 deletions src/stories/chat/parts/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import TextAlign from "@tiptap/extension-text-align";
import { KeyboardEvent as ReactKeyboardEvent, ReactNode } from "react";
import { useChatContext } from "../context/chatContext";
import { CustomMention as Mention } from "./mention";
import { MentionList, MentionListRef, SuggestedUser } from "./mentionList";
import { MentionList, MentionListRef } from "./mentionList";
import tippy, { type Instance as TippyInstance } from "tippy.js";
import { FauxInput } from "@zendeskgarden/react-forms";
import styled from "styled-components";
import { editorStyle, readOnlyStyle } from "../../shared/editorStyle";
import { SuggestedUser } from "../_types";

const EditorContainer = styled(FauxInput)<{ editable: boolean }>`
${({ editable, theme }) =>
Expand Down Expand Up @@ -82,10 +83,12 @@ const DOM_RECT_FALLBACK: DOMRect = {
export const CommentEditor = ({
placeholderOptions,
children,
mentionableUsers,
...props
}: {
placeholderOptions?: Partial<PlaceholderOptions>;
children?: ReactNode;
mentionableUsers: ({ query }: { query: string }) => Promise<SuggestedUser[]>;
} & Partial<EditorOptions>) => {
const { editor, setEditor, triggerSave } = useChatContext();
const isEditable = props.editable !== false;
Expand Down Expand Up @@ -119,60 +122,7 @@ export const CommentEditor = ({
}),
Mention.configure({
suggestion: {
items: ({ query }): SuggestedUser[] => {
return [
{
id: 1,
fullName: "John Doe",
avatar: "https://i.pravatar.cc/150?img=1",
},
{
id: 2,
fullName: "Jane Doe",
avatar: "https://i.pravatar.cc/150?img=2",
},
{
id: 3,
fullName: "John Smith",
avatar: "https://i.pravatar.cc/150?img=3",
},
{
id: 4,
fullName: "Jane Smith",
avatar: "https://i.pravatar.cc/150?img=4",
},
{
id: 5,
fullName: "Pippo Baudo",
avatar: "https://i.pravatar.cc/150?img=5",
},
{
id: 6,
fullName: "Pippo Franco",
avatar: "https://i.pravatar.cc/150?img=6",
},
{
id: 7,
fullName: "Pippo Inzaghi",
avatar: "https://i.pravatar.cc/150?img=7",
},
{
id: 8,
fullName: "Pippo Civati",
avatar: "https://i.pravatar.cc/150?img=8",
},
{
id: 9,
fullName: "Pippo Delbono",
avatar: "https://i.pravatar.cc/150?img=9",
},
].filter((item) => {
if (!query) return item;
return item.fullName
.toLowerCase()
.startsWith(query.toLowerCase());
});
},
items: mentionableUsers,
render: () => {
let component: ReactRenderer<MentionListRef> | undefined;
let popup: TippyInstance | undefined;
Expand All @@ -196,7 +146,7 @@ export const CommentEditor = ({
showOnCreate: true,
interactive: true,
trigger: "manual",
placement: "bottom-start",
placement: "auto",
})[0];
},

Expand Down
56 changes: 39 additions & 17 deletions src/stories/chat/parts/mentionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import type { SuggestionOptions, SuggestionProps } from "@tiptap/suggestion";
import { Card } from "../../cards";
import { Button } from "../../buttons/button";
import { Menu } from "../../dropdowns/menu";
import { Item } from "../../dropdowns/item";
import { styled } from "styled-components";
import { useChatContext } from "../context/chatContext";
import { SuggestedUser } from "../_types";

export type MentionListRef = {
onKeyDown: NonNullable<
Expand All @@ -11,7 +16,19 @@ export type MentionListRef = {
>;
};

export type SuggestedUser = { id: number; fullName: string; avatar: string };
const StyledCard = styled(Card)`
padding: ${({ theme }) => theme.space.xxs};
`;

const List = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.space.xs};
& .selected {
background-color: ${({ theme }) => theme.palette.grey[100]};
}
`;

type MentionListProps = SuggestionProps<SuggestedUser>;

Expand Down Expand Up @@ -66,22 +83,27 @@ export const MentionList = forwardRef<MentionListRef, MentionListProps>(

return (
<div className="items">
<Card>
{props.items.length ? (
props.items.map((item, index) => (
<Button
isAccent={index === selectedIndex}
key={index}
isPrimary
onClick={() => selectItem(index)}
>
{item.fullName}
</Button>
))
) : (
<div className="item">No result</div>
)}
</Card>
<StyledCard>
<List>
{props.items.length ? (
props.items.map((item, index) => (
<div className={index === selectedIndex ? 'selected': ''}>
<Button
isBasic
isStretched
isAccent
key={index}
onClick={() => selectItem(index)}
>
{item.fullName}
</Button>
</div>
))
) : (
<div className="item">No result</div>
)}
</List>
</StyledCard>
</div>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/stories/shared/editorStyle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,9 @@ export const editorStyle = css`
word-break: break-word;
mention {
color: ${({ theme }) => theme.palette.green[500]};
color: ${({ theme }) => theme.palette.azure[600]};
border-radius: ${({ theme }) => theme.borderRadii.xl};
font-weight: ${({ theme }) => theme.fontWeights.semibold};
padding: ${({ theme }) => `${theme.space.xxs} 0`};
}
`;
Expand Down

0 comments on commit d7f0040

Please sign in to comment.