Skip to content

Commit

Permalink
Merge pull request #296 from AppQuality/develop
Browse files Browse the repository at this point in the history
Chat mentions
  • Loading branch information
marcbon authored Jan 19, 2024
2 parents 2619310 + 5dafa4a commit 903981c
Show file tree
Hide file tree
Showing 20 changed files with 873 additions and 287 deletions.
19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@
"@nivo/sunburst": "^0.80.0",
"@nivo/tooltip": "^0.80.0",
"@nivo/waffle": "^0.80.0",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.61",
"@tiptap/extension-character-count": "^2.0.0-beta.31",
"@tiptap/extension-link": "^2.0.4",
"@tiptap/extension-placeholder": "^2.0.0-beta.53",
"@tiptap/extension-typography": "^2.0.0-beta.22",
"@tiptap/pm": "^2.0.3",
"@tiptap/react": "^2.0.0-beta.114",
"@tiptap/starter-kit": "^2.0.0-beta.191",
"@tiptap/extension-bubble-menu": "2.1.16",
"@tiptap/extension-character-count": "2.1.16",
"@tiptap/extension-link": "2.1.16",
"@tiptap/extension-mention": "2.1.16",
"@tiptap/extension-placeholder": "2.1.16",
"@tiptap/extension-typography": "2.1.16",
"@tiptap/pm": "2.1.16",
"@tiptap/react": "2.1.16",
"@tiptap/starter-kit": "2.1.16",
"@tiptap/suggestion": "2.1.16",
"@types/react-slick": "^0.23.10",
"@zendeskgarden/css-bedrock": "^9.0.0",
"@zendeskgarden/react-accordions": "^8.49.0",
Expand All @@ -63,6 +65,7 @@
"@zendeskgarden/react-typography": "^8.49.0",
"react-slick": "^0.29.0",
"react-window": "^1.8.6",
"tippy.js": "^6.3.7",
"ua-parser-js": "^1.0.2"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/at-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/stories/chat/_types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ import { BubbleMenuProps, EditorOptions } from "@tiptap/react";

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

export type SuggestedUser = { id: number; name: string; };

export interface ChatEditorArgs extends Partial<EditorOptions> {
placeholderOptions?: Partial<PlaceholderOptions>;
hasInlineMenu?: boolean;
hasButtonsMenu?: boolean;
bubbleOptions?: any;
author: Author;
i18n?: {
menu?: {
bold?: string;
italic?: string;
mention?: string;
};
};
}

export interface Author {
Expand Down
38 changes: 25 additions & 13 deletions src/stories/chat/context/chatContext.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
import { Editor } from "@tiptap/react";
import React, { createContext, useContext, useMemo, useState } from "react";
import { SuggestedUser } from "../_types";

export type ChatContextType = {
isEditing: boolean;
setIsEditing: (isEditing: boolean) => void;
comment: string;
setComment: (comment: string) => void;
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;
onSave?: (editor: Editor, mentions: SuggestedUser[]) => void;
children: React.ReactNode;
setMentionableUsers: (props: { query: string }) => Promise<SuggestedUser[]>;
}) => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [comment, setComment] = useState<string>("");
const [editor, setEditor] = useState<Editor | undefined>();

const getMentions = (editor: Editor) => {
const result: SuggestedUser[] = [];

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({
id: node.attrs.id,
name: node.attrs.name
});
}
});

return result;
};

const chatContextValue = useMemo(
() => ({
isEditing,
setIsEditing,
comment,
setComment,
editor,
setEditor,
triggerSave: () => {
if (editor && onSave) {
onSave(editor);
onSave(editor, getMentions(editor));
editor.commands.clearContent();
}
},
mentionableUsers: setMentionableUsers,
}),
[comment, setComment, isEditing, setIsEditing, editor, setEditor, onSave]
[editor, setEditor, onSave, setMentionableUsers]
);

return (
Expand Down
116 changes: 86 additions & 30 deletions src/stories/chat/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Meta, StoryFn } from "@storybook/react";
import { PlaceholderOptions } from "@tiptap/extension-placeholder";
import { Editor as TipTapEditor } from "@tiptap/react";
import styled from "styled-components";
import { Chat, ChatProvider, useChatContext } from ".";
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";

const ButtonsContainer = styled.div`
padding: 0px 16px;
display: flex;
`;
interface EditorStoryArgs extends ChatEditorArgs {
children?: any;
comments?: {
Expand All @@ -21,37 +26,94 @@ interface EditorStoryArgs extends ChatEditorArgs {
}[];
editorText?: string;
background?: string;
onSave: (editor: TipTapEditor) => void;
onSave: (editor: TipTapEditor, mentions: SuggestedUser[]) => void;
placeholderOptions?: Partial<PlaceholderOptions>;
}

const ChatPanel = ({ background, ...args }: EditorStoryArgs) => {
const { triggerSave } = useChatContext();
const { editor, triggerSave } = useChatContext();
return (
<Chat>
<Chat.Header>Titolone</Chat.Header>
<Chat.Comments chatBkg={background}>
{args.comments?.map((comment, index) => (
{args.comments?.map((comment) => (
<Comment {...comment}>
<>altre cose</>
</Comment>
))}
</Chat.Comments>
<Chat.Input {...args}>{args.editorText}</Chat.Input>
<Chat.Footer>
<Button isBasic>Cancel</Button>
<Button onClick={triggerSave}>Save</Button>
<Chat.Footer showShortcut>
<ButtonsContainer>
<Button isBasic onClick={() => editor?.commands.clearContent()}>
Cancel
</Button>
<Button onClick={triggerSave}>Save</Button>
</ButtonsContainer>
</Chat.Footer>
</Chat>
);
};

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

return (
<Grid>
<Row>
<Col xs={12} sm={6}>
<ChatProvider onSave={args.onSave}>
<Col xs={12} sm={8} md={6}>
<ChatProvider setMentionableUsers={getUsers} onSave={args.onSave}>
<ChatPanel {...args} />
</ChatProvider>
</Col>
Expand All @@ -63,8 +125,9 @@ const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
const defaultArgs: EditorStoryArgs = {
children:
"<p>I'm <em>a</em> <strong>stupid</strong> <code>editor</code>!</p>",
onSave: (editor: TipTapEditor) => {
onSave: (editor: TipTapEditor, mentions) => {
console.log("we have to save this", editor.getHTML());
console.log("mentions", mentions);
},
author: {
avatar: "LC",
Expand All @@ -85,31 +148,16 @@ const defaultArgs: EditorStoryArgs = {
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>",
message:
"Hi, I'm a comment too but with <strong>bold</strong>. lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>. lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong> askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap",
message: `Hi <mention data-type="mention" data-mention-id="1" data-mention-name="John Doe">@John Doe</mention>, I'm a comment too but with <strong>bold</strong> askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap`,
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
Expand Down Expand Up @@ -143,10 +191,18 @@ Placeholder.args = {
},
};

export const BubbleMenu = Template.bind({});
BubbleMenu.args = {
export const Menus = Template.bind({});
Menus.args = {
...defaultArgs,
hasInlineMenu: true,
hasButtonsMenu: true,
i18n: {
menu: {
bold: "Grassetto",
italic: "Corsivo",
mention: "Menziona",
},
},
};

export const CustomBackground = Template.bind({});
Expand Down
62 changes: 62 additions & 0 deletions src/stories/chat/parts/bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import styled from "styled-components";
import { Tooltip } from "../../tooltip";
import { ChatEditorArgs } from "../_types";
import { Editor } from "@tiptap/react";
import { EditorButton } from "./editorButton";

const MenuContainer = styled.div`
padding: ${({ theme }) => theme.space.xxs} 0;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
`;

const VerticalDivider = styled.div`
width: 2px;
height: 24px;
background-color: ${({ theme }) => theme.palette.grey[300]};
margin: 0 ${({ theme }) => theme.space.xs};
`;

const CommentBar = ({
editor,
i18n,
}: Partial<ChatEditorArgs> & {
editor?: Editor;
}) => {

if(!editor) return null;

return (
<MenuContainer>
<Tooltip
content={i18n?.menu?.bold ?? "Bold text"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="bold" />
</Tooltip>
<Tooltip
content={i18n?.menu?.italic ?? "Italic text"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="italic" />
</Tooltip>
<VerticalDivider />
<Tooltip
content={i18n?.menu?.mention ?? "Add a mention"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="mention" />
</Tooltip>
</MenuContainer>
);
};

export { CommentBar };
Loading

0 comments on commit 903981c

Please sign in to comment.