mr-chat-bird 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENCE +21 -0
  2. package/README.md +26 -0
  3. package/app/layout.tsx +69 -0
  4. package/app/page.tsx +9 -0
  5. package/dist/AddReaction-DCDVOMZB.svg +1 -0
  6. package/dist/TextFormat-R4ZVDKE2.svg +1 -0
  7. package/dist/index.css +388 -0
  8. package/dist/index.d.mts +5 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +6425 -0
  11. package/dist/index.mjs +6420 -0
  12. package/eslint.config.mjs +11 -0
  13. package/index.ts +1 -0
  14. package/next-env.d.ts +5 -0
  15. package/next.config.mjs +30 -0
  16. package/package.json +69 -0
  17. package/postcss.config.cjs +14 -0
  18. package/public/favicon.svg +1 -0
  19. package/src/components/ChatUserList/ChatUserList.module.css +253 -0
  20. package/src/components/ChatUserList/ChatUserList.tsx +434 -0
  21. package/src/components/ChatUserList/ChatUserList.type.tsx +12 -0
  22. package/src/components/ChatUserList/ChatUserMessage.tsx +362 -0
  23. package/src/components/ChatUserList/users_list.json +648 -0
  24. package/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx +15 -0
  25. package/src/components/EmojiPickerPopover/EmojiPickerPopover.tsx +72 -0
  26. package/src/components/MrChat/index.tsx +34 -0
  27. package/src/components/RichTextEditor/DropzoneMenuItem.tsx +33 -0
  28. package/src/components/RichTextEditor/EmojiNode.tsx +36 -0
  29. package/src/components/RichTextEditor/RichTextEditor.module.css +95 -0
  30. package/src/components/RichTextEditor/RichTextEditor.tsx +248 -0
  31. package/src/components/UserProfile/UserProfileDrawer.module.css +120 -0
  32. package/src/components/UserProfile/UserProfileDrawer.tsx +115 -0
  33. package/src/components/VirtualizedList/ChatScrollContainer.tsx +92 -0
  34. package/src/components/VirtualizedList/index.tsx +31 -0
  35. package/src/lib/axios.ts +12 -0
  36. package/src/lib/socket.ts +29 -0
  37. package/src/store/provider.tsx +8 -0
  38. package/src/store/slices/ChatSlice.ts +249 -0
  39. package/src/store/socket/index.tsx +32 -0
  40. package/src/store/store.ts +11 -0
  41. package/src/theme.ts +84 -0
  42. package/src/utils/environment.ts +5 -0
  43. package/src/utils/helper.ts +36 -0
  44. package/src/utils/icons/richText/Add.svg +1 -0
  45. package/src/utils/icons/richText/AddReaction.svg +1 -0
  46. package/src/utils/icons/richText/Docs.svg +1 -0
  47. package/src/utils/icons/richText/Image.svg +1 -0
  48. package/src/utils/icons/richText/TextFormat.svg +1 -0
  49. package/tsconfig.json +25 -0
@@ -0,0 +1,72 @@
1
+ import {
2
+ useState,
3
+ lazy,
4
+ Suspense,
5
+ cloneElement,
6
+ MouseEventHandler,
7
+ ReactElement,
8
+ } from "react";
9
+ import { Popover, Skeleton } from "@mantine/core";
10
+ import data from "@emoji-mart/data";
11
+ const EmojiPicker = lazy(() => import("@emoji-mart/react"));
12
+
13
+ type EmojiPickerPopoverProps = {
14
+ onSelect: (emoji: string) => void;
15
+ action: ReactElement<{ onClick?: MouseEventHandler<any> }>;
16
+ size?: number;
17
+ offset?: number;
18
+ position?: "bottom" | "top" | "left" | "right" | "bottom-start" | "top-end";
19
+ };
20
+
21
+ export default function EmojiPickerPopover({
22
+ onSelect,
23
+ action,
24
+ size = 320,
25
+ offset = 8,
26
+ position = "bottom-start",
27
+ }: EmojiPickerPopoverProps) {
28
+ const [opened, setOpened] = useState(false);
29
+
30
+ return (
31
+ <Popover
32
+ opened={opened}
33
+ onChange={setOpened}
34
+ width={size}
35
+ position={position}
36
+ offset={offset}
37
+ withArrow
38
+ shadow="md"
39
+ trapFocus={false}
40
+ closeOnEscape
41
+ >
42
+ <Popover.Target>
43
+ {cloneElement(action, {
44
+ onClick: (e) => {
45
+ action.props?.onClick?.(e);
46
+ setOpened((prev) => !prev);
47
+ },
48
+ })}
49
+ </Popover.Target>
50
+
51
+ <Popover.Dropdown p={0}>
52
+ <Suspense
53
+ fallback={
54
+ <div style={{ padding: 12, width: 320 }}>
55
+ <Skeleton height={280} radius="md" />
56
+ </div>
57
+ }
58
+ >
59
+ <EmojiPicker
60
+ previewPosition={"none"}
61
+ theme="light"
62
+ data={data}
63
+ onEmojiSelect={(emoji: any) => {
64
+ onSelect(emoji.native);
65
+ setOpened(false);
66
+ }}
67
+ />
68
+ </Suspense>
69
+ </Popover.Dropdown>
70
+ </Popover>
71
+ );
72
+ }
@@ -0,0 +1,34 @@
1
+ // app/components/MrChat.tsx
2
+ "use client";
3
+
4
+ import "@mantine/core/styles.css";
5
+ import "@mantine/tiptap/styles.css";
6
+ import "@mantine/notifications/styles.css";
7
+
8
+ import { useState } from "react";
9
+ import { MantineProvider } from "@mantine/core";
10
+ import { Notifications } from "@mantine/notifications";
11
+ import { createAppTheme } from "../../theme";
12
+ import { ReduxProvider } from "../../store/provider";
13
+ import { SocketProvider } from "../../store/socket";
14
+ import { ChatUserList } from "../../components/ChatUserList/ChatUserList";
15
+
16
+
17
+ export default function MrChat(props: any) {
18
+ const [primaryColor] = useState<
19
+ "customBrand" | "emerald" | "ruby" | "sunset" | "ocean"
20
+ >("customBrand");
21
+
22
+ const theme = createAppTheme(primaryColor);
23
+
24
+ return (
25
+ <ReduxProvider>
26
+ <SocketProvider>
27
+ <MantineProvider defaultColorScheme="light" theme={theme}>
28
+ <Notifications />
29
+ <ChatUserList {...props} />
30
+ </MantineProvider>
31
+ </SocketProvider>
32
+ </ReduxProvider>
33
+ );
34
+ }
@@ -0,0 +1,33 @@
1
+ import { Menu } from "@mantine/core";
2
+ import { ReactNode } from "react";
3
+ import { useDropzone } from "react-dropzone";
4
+ import classes from "./RichTextEditor.module.css";
5
+
6
+ export function DropzoneMenuItem({
7
+ label,
8
+ icon,
9
+ accept,
10
+ onDrop,
11
+ }: {
12
+ label: string;
13
+ icon: ReactNode;
14
+ accept?: string;
15
+ onDrop: (file: File) => void;
16
+ }) {
17
+ const { getRootProps, getInputProps } = useDropzone({
18
+ accept: accept ? { [accept]: [] } : undefined,
19
+ multiple: false,
20
+ onDropAccepted: (files) => {
21
+ if (files.length > 0) onDrop(files[0]);
22
+ },
23
+ });
24
+
25
+ return (
26
+ <div {...getRootProps()} style={{ outline: "none" }}>
27
+ <input {...getInputProps()} />
28
+ <Menu.Item leftSection={icon} className={classes.mainAddLabels}>
29
+ {label}
30
+ </Menu.Item>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,36 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
2
+
3
+ export const EmojiNode = Node.create({
4
+ name: "emoji",
5
+ inline: true,
6
+ group: "inline",
7
+ selectable: false,
8
+ atom: true,
9
+
10
+ addAttributes() {
11
+ return {
12
+ emoji: {
13
+ default: "😀",
14
+ },
15
+ };
16
+ },
17
+
18
+ parseHTML() {
19
+ return [
20
+ {
21
+ tag: "span[data-emoji]",
22
+ },
23
+ ];
24
+ },
25
+
26
+ renderHTML({ HTMLAttributes }) {
27
+ return [
28
+ "span",
29
+ mergeAttributes(HTMLAttributes, {
30
+ class: "emoji-big",
31
+ "data-emoji": HTMLAttributes.emoji,
32
+ }),
33
+ HTMLAttributes.emoji,
34
+ ];
35
+ },
36
+ });
@@ -0,0 +1,95 @@
1
+ .richTextEditorContainer {
2
+ position: relative;
3
+ border-radius: 16px;
4
+ }
5
+
6
+ .richTextEditorContainer :global(.mantine-RichTextEditor-toolbar) {
7
+ border: none;
8
+ background-color: transparent;
9
+ padding: 0;
10
+ margin-bottom: 4px;
11
+ margin-left: 3px;
12
+ gap: 0.5rem;
13
+ }
14
+
15
+ .richTextEditorContainer :global(.emoji-big) {
16
+ font-size: 18px;
17
+ line-height: 1;
18
+ vertical-align: middle;
19
+ display: inline-block;
20
+ }
21
+
22
+ .richTextEditorContainer :global(.mantine-RichTextEditor-Typography) {
23
+ font-size: 15px;
24
+ }
25
+
26
+ .richTextEditorContainer :global(.mantine-RichTextEditor-Typography),
27
+ .richTextEditorContainer :global(.mantine-RichTextEditor-content) {
28
+ border-radius: 16px;
29
+ min-height: 41px;
30
+ max-height: 200px;
31
+ overflow: scroll;
32
+ }
33
+
34
+ .richTextEditorContainer :global(.ProseMirror) {
35
+ padding: 12px 16px 4px;
36
+ }
37
+
38
+ .mainActionContainer {
39
+ height: 24px;
40
+ margin-bottom: 4px;
41
+ }
42
+
43
+ .mainActionAddIcon {
44
+ margin-left: 3px;
45
+ }
46
+
47
+ .mainActionEmojiIcon {
48
+ margin: 0 4px 0 3px;
49
+ }
50
+
51
+ .mainActionEmojiIcon svg {
52
+ padding: 2.3px;
53
+ }
54
+
55
+ .mainActionAddIcon svg,
56
+ .mainTextFormatIcon svg,
57
+ .mainActionEmojiIcon svg {
58
+ fill: #495057;
59
+ }
60
+
61
+ .mainTextFormatIcon svg {
62
+ position: relative;
63
+ top: 1.2px;
64
+ }
65
+
66
+ .mainSendMsgIcon {
67
+ position: absolute;
68
+ right: 6px;
69
+ }
70
+
71
+ .customToolActionItem {
72
+ background-color: var(--mantine-color-white);
73
+ border-color: var(--mantine-color-gray-4);
74
+ color: var(--mantine-color-gray-7);
75
+ border-radius: 0px;
76
+ height: 26px;
77
+ min-height: 26px;
78
+ border-right: none;
79
+ }
80
+
81
+ .mainAddLabels {
82
+ font-size: 15px;
83
+ /* color: var(--mantine-color-customBrand-filled); */
84
+ }
85
+
86
+ .mainAddLabels :global(svg) {
87
+ width: 19px;
88
+ height: 19px;
89
+ }
90
+
91
+ @media (max-width: 950px) {
92
+ .secondRichTextEditorToolBar {
93
+ display: none;
94
+ }
95
+ }
@@ -0,0 +1,248 @@
1
+ import { Suspense, lazy, useState } from "react";
2
+ import {
3
+ IconSend,
4
+ IconTextSize,
5
+ IconAlignLeft,
6
+ IconAlignCenter,
7
+ IconAlignRight,
8
+ IconPhotoPlus,
9
+ } from "@tabler/icons-react";
10
+ import Highlight from "@tiptap/extension-highlight";
11
+ import SubScript from "@tiptap/extension-subscript";
12
+ import Superscript from "@tiptap/extension-superscript";
13
+ import TextAlign from "@tiptap/extension-text-align";
14
+ import Underline from "@tiptap/extension-underline";
15
+ import { useEditor } from "@tiptap/react";
16
+ import StarterKit from "@tiptap/starter-kit";
17
+ import { Link, RichTextEditor } from "@mantine/tiptap";
18
+ import { ActionIcon, Menu } from "@mantine/core";
19
+ import EmojiPickerPopover from "../EmojiPickerPopover/EmojiPickerPopover";
20
+
21
+ import AddIcon from "../../utils/icons/richText/Add.svg";
22
+ import DocsIcon from "../../utils/icons/richText/Docs.svg";
23
+ import TextFormatIcon from "../../utils/icons/richText/TextFormat.svg";
24
+ import AddReactionIcon from "../../utils/icons/richText/AddReaction.svg";
25
+
26
+ import classes from "./RichTextEditor.module.css";
27
+ import { EmojiNode } from "./EmojiNode";
28
+ import { DropzoneMenuItem } from "./DropzoneMenuItem";
29
+
30
+ export default function CustomRichTextEditor({
31
+ content = "",
32
+ maxLength = 3000,
33
+ onChange,
34
+ onSubmit,
35
+ }: {
36
+ content?: string;
37
+ maxLength?: number;
38
+ onChange?: (value: string) => void;
39
+ onSubmit?: (value: string) => void;
40
+ }) {
41
+ const [charCount, setCharCount] = useState(0);
42
+ const [showWarning, setShowWarning] = useState(false);
43
+ const [showToolbar, setShowToolbar] = useState(false);
44
+
45
+ const editor = useEditor({
46
+ extensions: [
47
+ StarterKit,
48
+ EmojiNode,
49
+ Underline,
50
+ Link,
51
+ Superscript,
52
+ SubScript,
53
+ Highlight,
54
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
55
+ ],
56
+ content,
57
+ onUpdate: ({ editor }) => {
58
+ const text = editor.getText();
59
+ setCharCount(text.length);
60
+
61
+ if (maxLength && text.length >= maxLength) {
62
+ setShowWarning(true);
63
+ } else {
64
+ setShowWarning(false);
65
+ }
66
+
67
+ if (onChange) onChange(editor.getHTML());
68
+ },
69
+ immediatelyRender: false,
70
+ });
71
+
72
+ const handleSubmitMsg = () => {
73
+ if (!editor) return;
74
+
75
+ const message = editor.getHTML();
76
+
77
+ if (!editor.getText().trim()) return;
78
+
79
+ onSubmit?.(message);
80
+
81
+ editor.commands.clearContent();
82
+ editor.commands.focus();
83
+ setCharCount(0);
84
+ };
85
+
86
+ return (
87
+ <>
88
+ <RichTextEditor
89
+ editor={editor}
90
+ w="100%"
91
+ className={classes.richTextEditorContainer}
92
+ >
93
+ <RichTextEditor.Content />
94
+
95
+ <RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
96
+ <div className={classes.mainActionContainer}>
97
+ {/* <Menu shadow="md" width={160} position="bottom-start" offset={8}>
98
+ <Menu.Target>
99
+ <ActionIcon
100
+ radius="lg"
101
+ variant="subtle"
102
+ aria-label="add"
103
+ color="#333"
104
+ className={classes.mainActionAddIcon}
105
+ >
106
+ <AddIcon />
107
+ </ActionIcon>
108
+ </Menu.Target>
109
+
110
+ <Menu.Dropdown>
111
+ <DropzoneMenuItem
112
+ label="Document"
113
+ icon={<DocsIcon />}
114
+ onDrop={(file) => console.log("Document:", file)}
115
+ />
116
+ <DropzoneMenuItem
117
+ label="Image"
118
+ icon={<IconPhotoPlus stroke={1.5} className={classes.mainAddImageIcon} />}
119
+ accept="image/*"
120
+ onDrop={(file) => console.log("Photo:", file)}
121
+ />
122
+ </Menu.Dropdown>
123
+ </Menu> */}
124
+
125
+ <EmojiPickerPopover
126
+ onSelect={(emoji) => {
127
+ editor
128
+ ?.chain()
129
+ .focus()
130
+ .insertContent({
131
+ type: "emoji",
132
+ attrs: {
133
+ emoji,
134
+ },
135
+ })
136
+ .run();
137
+ }}
138
+ action={
139
+ <ActionIcon
140
+ radius="lg"
141
+ variant="subtle"
142
+ aria-label="add"
143
+ color="#333"
144
+ className={classes.mainActionEmojiIcon}
145
+ >
146
+ <AddReactionIcon />
147
+ </ActionIcon>
148
+ }
149
+ />
150
+ <ActionIcon
151
+ radius="lg"
152
+ variant="subtle"
153
+ aria-label="text_format"
154
+ color="#333"
155
+ className={classes.mainTextFormatIcon}
156
+ onClick={() => setShowToolbar((prev) => !prev)}
157
+ >
158
+ <TextFormatIcon />
159
+ </ActionIcon>
160
+ <ActionIcon
161
+ radius="lg"
162
+ variant="filled"
163
+ aria-label="send"
164
+ className={classes.mainSendMsgIcon}
165
+ onClick={() => handleSubmitMsg()}
166
+ >
167
+ <IconSend size={18} />
168
+ </ActionIcon>
169
+ </div>
170
+ {showToolbar && (
171
+ <>
172
+ <RichTextEditor.ControlsGroup>
173
+ <RichTextEditor.Bold />
174
+ <RichTextEditor.Italic />
175
+ <RichTextEditor.Underline />
176
+ <RichTextEditor.Strikethrough />
177
+ <RichTextEditor.Code />
178
+ <RichTextEditor.Highlight />
179
+ </RichTextEditor.ControlsGroup>
180
+
181
+ <RichTextEditor.ControlsGroup
182
+ className={classes.secondRichTextEditorToolBar}
183
+ >
184
+ <RichTextEditor.BulletList />
185
+ <RichTextEditor.OrderedList />
186
+ <Menu trigger="hover" openDelay={200}>
187
+ <Menu.Target>
188
+ <ActionIcon
189
+ variant="outline"
190
+ size="md"
191
+ color="#495057"
192
+ className={classes.customToolActionItem}
193
+ >
194
+ <IconAlignLeft size={16} stroke={1.5} />
195
+ </ActionIcon>
196
+ </Menu.Target>
197
+
198
+ <Menu.Dropdown>
199
+ <Menu.Item
200
+ onClick={() =>
201
+ editor?.chain().focus().setTextAlign("left").run()
202
+ }
203
+ >
204
+ <IconAlignLeft size={14} />
205
+ </Menu.Item>
206
+ <Menu.Item
207
+ onClick={() =>
208
+ editor?.chain().focus().setTextAlign("center").run()
209
+ }
210
+ >
211
+ <IconAlignCenter size={14} />
212
+ </Menu.Item>
213
+ <Menu.Item
214
+ onClick={() =>
215
+ editor?.chain().focus().setTextAlign("right").run()
216
+ }
217
+ >
218
+ <IconAlignRight size={14} />
219
+ </Menu.Item>
220
+ </Menu.Dropdown>
221
+ </Menu>
222
+ <RichTextEditor.Link />
223
+ </RichTextEditor.ControlsGroup>
224
+
225
+ <RichTextEditor.ControlsGroup
226
+ className={classes.secondRichTextEditorToolBar}
227
+ >
228
+ <RichTextEditor.ClearFormatting />
229
+ </RichTextEditor.ControlsGroup>
230
+ </>
231
+ )}
232
+ </RichTextEditor.Toolbar>
233
+ {showWarning && (
234
+ <div
235
+ style={{
236
+ textAlign: "right",
237
+ color: "red",
238
+ fontSize: 12,
239
+ marginTop: 4,
240
+ }}
241
+ >
242
+ Character limit reached ({charCount}/{maxLength})
243
+ </div>
244
+ )}
245
+ </RichTextEditor>
246
+ </>
247
+ );
248
+ }
@@ -0,0 +1,120 @@
1
+ /* Overlay */
2
+ .overlay {
3
+ position: absolute;
4
+ inset: 0;
5
+ background: rgba(0, 0, 0, 0.3);
6
+ z-index: 50;
7
+ }
8
+
9
+ /* Drawer */
10
+ .drawer {
11
+ position: absolute;
12
+ top: 0;
13
+ right: 0;
14
+ width: 100%;
15
+ max-width: 400px;
16
+ height: 100%;
17
+ background: #fff;
18
+
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+
23
+ /* Animations */
24
+ .open {
25
+ animation: slideIn 0.25s ease forwards;
26
+ }
27
+
28
+ .close {
29
+ animation: slideOut 0.25s ease forwards;
30
+ }
31
+
32
+ @keyframes slideIn {
33
+ from {
34
+ transform: translateX(100%);
35
+ }
36
+ to {
37
+ transform: translateX(0);
38
+ }
39
+ }
40
+
41
+ @keyframes slideOut {
42
+ from {
43
+ transform: translateX(0);
44
+ }
45
+ to {
46
+ transform: translateX(100%);
47
+ }
48
+ }
49
+
50
+ /* Header */
51
+ .header {
52
+ height: 60px;
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 15px;
56
+ padding: 0 16px;
57
+ border-bottom: 1px solid #eee;
58
+ }
59
+
60
+ .backBtn {
61
+ cursor: pointer;
62
+ display: flex;
63
+ align-items: center;
64
+ }
65
+
66
+ /* Profile */
67
+ .profileSection {
68
+ padding: 30px 20px;
69
+ border-bottom: 1px solid #f0f0f0;
70
+
71
+ display: flex;
72
+ flex-direction: column;
73
+ align-items: center;
74
+ text-align: center;
75
+ }
76
+
77
+ /* Actions */
78
+ .actions {
79
+ padding: 10px;
80
+ }
81
+
82
+ .item {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 12px;
86
+ padding: 14px;
87
+ border-radius: 10px;
88
+ cursor: pointer;
89
+ }
90
+
91
+ .item:hover {
92
+ background: #f5f5f5;
93
+ }
94
+
95
+ /* RED actions */
96
+ .itemDanger {
97
+ color: #fa5252;
98
+ }
99
+
100
+ .itemDanger:hover {
101
+ background: rgba(250, 82, 82, 0.1);
102
+ }
103
+
104
+ /* Image Preview */
105
+ .imagePreview {
106
+ position: fixed;
107
+ inset: 0;
108
+ background: black;
109
+ z-index: 100;
110
+
111
+ display: flex;
112
+ justify-content: center;
113
+ align-items: center;
114
+ }
115
+
116
+ .imagePreview img {
117
+ max-width: 90%;
118
+ max-height: 90%;
119
+ border-radius: 12px;
120
+ }