blixify-ui-web-mcp 0.1.2 → 0.1.18

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 (135) hide show
  1. package/lib/components/action/button/README.md +12 -11
  2. package/lib/components/action/button/button.stories.tsx +4 -10
  3. package/lib/components/action/buttonGroup/buttonGroup.stories.tsx +4 -10
  4. package/lib/components/action/iconButton/iconButton.stories.tsx +4 -10
  5. package/lib/components/action/radioList/radioList.stories.tsx +4 -10
  6. package/lib/components/action/slider/slider.stories.tsx +4 -10
  7. package/lib/components/action/textButton/textButton.stories.tsx +4 -10
  8. package/lib/components/action/toggle/toggle.stories.tsx +4 -10
  9. package/lib/components/advanced/collapse/collapse.stories.tsx +4 -10
  10. package/lib/components/advanced/infoWindow/infoWindow.stories.tsx +4 -10
  11. package/lib/components/advanced/popUp/README.md +21 -7
  12. package/lib/components/advanced/popUp/popUp.stories.tsx +4 -10
  13. package/lib/components/auth/bareSignIn/bareSignIn.stories.tsx +30 -10
  14. package/lib/components/auth/bareSignUp/bareSignUp.stories.tsx +4 -10
  15. package/lib/components/auth/signIn/signIn.stories.tsx +4 -10
  16. package/lib/components/auth/signUp/signUp.stories.tsx +4 -10
  17. package/lib/components/chart/areaChart/areaChart.stories.tsx +7 -13
  18. package/lib/components/chart/barChart/barChart.stories.tsx +7 -13
  19. package/lib/components/chart/boxPlotChart/boxPlotChart.stories.tsx +8 -14
  20. package/lib/components/chart/bubbleChart/bubbleChart.stories.tsx +8 -14
  21. package/lib/components/chart/card/card.stories.tsx +4 -10
  22. package/lib/components/chart/ganttChart/README.md +96 -110
  23. package/lib/components/chart/ganttChart/ganttChart.stories.tsx +135 -125
  24. package/lib/components/chart/heatmapChart/README.md +93 -82
  25. package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +122 -84
  26. package/lib/components/chart/lineChart/lineChart.stories.tsx +4 -10
  27. package/lib/components/chart/pieChart/pieChart.stories.tsx +4 -10
  28. package/lib/components/chart/polarAreaChart/polarAreaChart.stories.tsx +8 -14
  29. package/lib/components/chart/radarChart/radarChart.stories.tsx +4 -10
  30. package/lib/components/chart/radialBar/radialChart.stories.tsx +4 -10
  31. package/lib/components/chart/rangeBarChart/rangeBarChart.stories.tsx +4 -10
  32. package/lib/components/chart/statsCard/statsCard.stories.tsx +4 -10
  33. package/lib/components/chart/treemapChart/treemapChart.stories.tsx +8 -14
  34. package/lib/components/chat/chatAnchor/README.md +61 -79
  35. package/lib/components/chat/chatAnchor/chatAnchor.stories.tsx +13 -19
  36. package/lib/components/chat/chatBase/README.md +244 -196
  37. package/lib/components/chat/chatBase/chatBase.stories.tsx +828 -250
  38. package/lib/components/chat/chatDashboard/README.md +89 -129
  39. package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +406 -117
  40. package/lib/components/data/dataGraph/README.md +135 -1
  41. package/lib/components/data/dataGraph/dataGraph.stories.tsx +90 -77
  42. package/lib/components/data/dataReference/README.md +141 -43
  43. package/lib/components/data/dataReference/dataReference.stories.tsx +111 -45
  44. package/lib/components/data/dataTemplate/README.md +88 -61
  45. package/lib/components/data/dataTemplate/dataModule.stories.tsx +4 -10
  46. package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +4 -10
  47. package/lib/components/data/dataWrapper/README.md +48 -46
  48. package/lib/components/data/dataWrapper/dataWrapper.stories.tsx +258 -489
  49. package/lib/components/data/queryForm/queryForm.stories.tsx +4 -10
  50. package/lib/components/data/readQuery/readQuery.stories.tsx +4 -9
  51. package/lib/components/data/readQueryHoc/readQueryHoc.stories.tsx +4 -10
  52. package/lib/components/data/writeQuery/writeQuery.stories.tsx +4 -8
  53. package/lib/components/design/actionSection/actionSection.stories.tsx +6 -10
  54. package/lib/components/design/banner/banner.stories.tsx +4 -10
  55. package/lib/components/design/bannerAction/bannerAction.stories.tsx +4 -10
  56. package/lib/components/design/blogSection/blogSection.stories.tsx +4 -10
  57. package/lib/components/design/carouselSection/carouselSection.stories.tsx +4 -10
  58. package/lib/components/design/designList/designList.stories.tsx +4 -10
  59. package/lib/components/design/faq/faq.stories.tsx +4 -10
  60. package/lib/components/design/fileGrid/fileGrid.stories.tsx +4 -10
  61. package/lib/components/design/fileViewer/fileViewer.stories.tsx +4 -10
  62. package/lib/components/design/footerSection/footerSection.stories.tsx +4 -10
  63. package/lib/components/design/googleReview/googleReview.stories.tsx +4 -10
  64. package/lib/components/design/heroSection/heroSection.stories.tsx +4 -10
  65. package/lib/components/design/iconList/iconList.stories.tsx +4 -10
  66. package/lib/components/design/imageGallery/imageGallery.stories.tsx +4 -10
  67. package/lib/components/design/imageZoom/imageZoom.stories.tsx +4 -10
  68. package/lib/components/design/leftContent/leftContent.stories.tsx +4 -10
  69. package/lib/components/design/logoCloud/logoCloud.stories.tsx +4 -10
  70. package/lib/components/design/onboarding/onboarding.stories.tsx +4 -10
  71. package/lib/components/design/pricing/pricing.stories.tsx +4 -10
  72. package/lib/components/design/rightContent/rightContent.stories.tsx +4 -10
  73. package/lib/components/design/sectionList/sectionList.stories.tsx +4 -10
  74. package/lib/components/design/teams/teams.stories.tsx +4 -10
  75. package/lib/components/display/alert/alert.stories.tsx +4 -10
  76. package/lib/components/display/calendar/README.md +49 -0
  77. package/lib/components/display/calendar/calendar.stories.tsx +71 -28
  78. package/lib/components/display/clipboard/clipboard.stories.tsx +4 -10
  79. package/lib/components/display/detailList/detailList.stories.tsx +5 -10
  80. package/lib/components/display/drawer/drawer.stories.tsx +5 -10
  81. package/lib/components/display/emptyState/emptyState.stories.tsx +4 -10
  82. package/lib/components/display/feeds/feeds.stories.tsx +4 -10
  83. package/lib/components/display/flexTable/README.md +11 -7
  84. package/lib/components/display/flexTable/flexTable.stories.tsx +114 -11
  85. package/lib/components/display/grid/README.md +28 -27
  86. package/lib/components/display/grid/grid.stories.tsx +4 -10
  87. package/lib/components/display/kanban/kanban.stories.tsx +14 -20
  88. package/lib/components/display/list/README.md +30 -29
  89. package/lib/components/display/list/list.stories.tsx +4 -10
  90. package/lib/components/display/loading/loading.stories.tsx +5 -10
  91. package/lib/components/display/mapView/mapView.stories.tsx +4 -10
  92. package/lib/components/display/modal/modal.stories.tsx +4 -11
  93. package/lib/components/display/notification/notification.stories.tsx +4 -10
  94. package/lib/components/display/progress/progress.stories.tsx +5 -10
  95. package/lib/components/display/stepper/stepper.stories.tsx +5 -10
  96. package/lib/components/display/table/README.md +38 -37
  97. package/lib/components/display/table/table.stories.tsx +8 -14
  98. package/lib/components/display/tag/tag.stories.tsx +4 -10
  99. package/lib/components/input/address/address.stories.tsx +4 -10
  100. package/lib/components/input/canvas/canvas.stories.tsx +4 -10
  101. package/lib/components/input/checkbox/checkbox.stories.tsx +4 -10
  102. package/lib/components/input/datePicker/datePicker.stories.tsx +4 -10
  103. package/lib/components/input/form/form.stories.tsx +4 -10
  104. package/lib/components/input/markdown/markdown.stories.tsx +5 -10
  105. package/lib/components/input/monthYearPicker/monthYearPicker.stories.tsx +4 -10
  106. package/lib/components/input/netPromoterScore/netPromoterScore.stories.tsx +4 -10
  107. package/lib/components/input/rating/rating.stories.tsx +4 -10
  108. package/lib/components/input/select/README.md +30 -29
  109. package/lib/components/input/select/select.stories.tsx +4 -10
  110. package/lib/components/input/signature/signature.stories.tsx +4 -10
  111. package/lib/components/input/textArea/textArea.stories.tsx +4 -10
  112. package/lib/components/input/textInput/textinput.stories.tsx +4 -10
  113. package/lib/components/input/uploadInput/uploadInput.stories.tsx +135 -11
  114. package/lib/components/navigation/bars/README.md +90 -17
  115. package/lib/components/navigation/bars/bars.stories.tsx +128 -60
  116. package/lib/components/navigation/bottomTab/bottomTab.stories.tsx +5 -10
  117. package/lib/components/navigation/breadcrumb/README.md +40 -20
  118. package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +34 -14
  119. package/lib/components/navigation/header/README.md +67 -108
  120. package/lib/components/navigation/header/header.stories.tsx +165 -49
  121. package/lib/components/navigation/leftNav/README.md +121 -66
  122. package/lib/components/navigation/leftNav/leftNav.stories.tsx +178 -91
  123. package/lib/components/navigation/tabs/tabs.stories.tsx +57 -39
  124. package/lib/components/structure/column/column.stories.tsx +5 -10
  125. package/lib/components/structure/container/container.stories.tsx +5 -10
  126. package/lib/components/structure/link/link.stories.tsx +4 -10
  127. package/lib/components/structure/text/text.stories.tsx +5 -10
  128. package/lib/components/template/authPage/authPage.stories.tsx +6 -5
  129. package/lib/components/template/landingPage/landingPage.stories.tsx +8 -6
  130. package/lib/components/template/productListing/productListing.stories.tsx +7 -13
  131. package/lib/components/template/productOverview/productOverview.stories.tsx +5 -11
  132. package/lib/components/template/shoppingCart/shoppingCart.stories.tsx +6 -12
  133. package/lib/components/template/userHomePage/userHomePage.stories.tsx +7 -6
  134. package/lib/components/template/userRankingDashboard/userRankingDashboard.stories.tsx +5 -11
  135. package/package.json +1 -1
@@ -1,154 +1,443 @@
1
- import { ChevronLeftIcon } from "@heroicons/react/24/outline";
2
- import { storiesOf } from "@storybook/react";
3
- import moment from "moment";
1
+ import {
2
+ ChevronLeftIcon,
3
+ PencilSquareIcon,
4
+ TrashIcon,
5
+ } from "@heroicons/react/24/outline";
6
+ import dayjs from "../../dayjs";
4
7
  import React, { useEffect, useState } from "react";
8
+ import { v4 as uuidv4 } from "uuid";
5
9
  import { ChatDashboard } from ".";
6
- import Logo from "../../../assets/logo.png";
10
+ import { StorySection } from "../../storyUtils";
7
11
  import { IconButton } from "../../action/iconButton";
8
12
  import { Loading } from "../../display/loading";
9
- import { Conversation } from "../chatBase";
10
- import { conversationList as convoList, currentUser } from "../chatUtils";
11
- import README from "./README.md";
13
+ import { ChatBaseAction, Conversation } from "../chatBase";
14
+ import { conversationList as seedConvoList, currentUser } from "../chatUtils";
15
+ import { Button } from "../../action/button";
12
16
 
13
- const stories = storiesOf("Chat", module);
17
+ const meta = { title: "Chat/Chat Dashboard" };
18
+ export default meta;
14
19
 
15
- stories.addParameters({
16
- readme: {
17
- content: README,
18
- },
19
- });
20
20
 
21
- stories.add("Chat Dashboard", () => {
21
+ const sortByLatest = (list: Conversation[]) =>
22
+ [...list].sort((a, b) =>
23
+ dayjs(b.messageList[b.messageList.length - 1].createdAt).diff(
24
+ dayjs(a.messageList[a.messageList.length - 1].createdAt),
25
+ ),
26
+ );
27
+
28
+ // INFO: demo statuses for renderConversationMeta / renderHeaderTags
29
+ const DEMO_STATUSES = ["active", "waiting", "closed", "assigned"];
30
+ const DEMO_BADGE_COLORS: Record<string, string> = {
31
+ active: "bg-green-100 text-green-700",
32
+ waiting: "bg-yellow-100 text-yellow-700",
33
+ closed: "bg-gray-100 text-gray-500",
34
+ assigned: "bg-blue-100 text-blue-700",
35
+ };
36
+
37
+ export const ChatDashboardStory = () => {
38
+ // INFO: Section 1 — Base chat state
22
39
  const [message, setMessage] = useState("");
23
40
  const [search, setSearch] = useState("");
24
41
  const [isLoading, setIsLoading] = useState(false);
25
42
  const [mobileSelectedChat, setMobileSelectedChat] = useState(false);
26
43
  const [filteredConversation, setFilteredConversation] =
27
- useState<Conversation | null>();
44
+ useState<Conversation | null>(null);
45
+ const [conversationList, setConversationList] = useState<Conversation[]>([]);
28
46
  const [conversation, setConversation] = useState<Conversation>({
29
47
  id: "",
30
48
  members: [],
31
49
  messageList: [],
32
50
  });
33
- const [conversationList, setConversationList] = useState<Conversation[]>([]);
51
+ const [file, setFile] = useState<File | any>(undefined);
34
52
 
35
- useEffect(() => {
36
- handleSortConvoList(convoList);
37
- }, []);
53
+ // INFO: Section 2 — Meta & Tags state
54
+ const [metaConvoList, setMetaConvoList] = useState<Conversation[]>([]);
55
+ const [metaConvo, setMetaConvo] = useState<Conversation>({
56
+ id: "",
57
+ members: [],
58
+ messageList: [],
59
+ });
60
+ const [metaMessage, setMetaMessage] = useState("");
38
61
 
39
- const handleOnSearch = (searchMessage: string) => {
40
- if (searchMessage.length === 0) {
41
- setSearch("");
42
- setFilteredConversation(null);
43
- } else {
44
- const dupMessageList = conversation.messageList.filter((eachMessage) => {
45
- if (typeof eachMessage.messageBody === "string") {
46
- return eachMessage.messageBody
47
- .toLowerCase()
48
- .includes(searchMessage.toLowerCase());
49
- }
50
- return null;
51
- });
52
- const dupConversation = {
53
- id: conversation.id,
54
- members: [...conversation.members],
55
- messageList: [...dupMessageList],
56
- };
57
- setFilteredConversation(dupConversation);
58
- }
59
- };
62
+ // INFO: Section 3 — Pagination state
63
+ const [paginationConvoList, setPaginationConvoList] = useState<
64
+ Conversation[]
65
+ >([]);
66
+ const [paginationConvo, setPaginationConvo] = useState<Conversation>({
67
+ id: "",
68
+ members: [],
69
+ messageList: [],
70
+ });
71
+ const [paginationMessage, setPaginationMessage] = useState("");
72
+ const [page, setPage] = useState(1);
73
+ const [loadingConversations, setLoadingConversations] = useState(false);
74
+ const [loadingMessages, setLoadingMessages] = useState(false);
75
+ const [msgPage, setMsgPage] = useState(1);
60
76
 
61
- const handleOnSendMessage = (conversation: Conversation) => {
62
- setConversation({
63
- id: conversation.id,
64
- members: [...conversation.members],
65
- messageList: [...conversation.messageList],
66
- });
67
- setMessage("");
68
- const dupConvoList = conversationList.map((eachConvo, i) => {
69
- if (eachConvo.id === conversation.id) {
70
- return conversation;
71
- }
72
- return eachConvo;
77
+ // INFO: pageNum 0 generates 15 contacts to ensure the list overflows and is scrollable
78
+ const generateConversations = (pageNum: number): Conversation[] => {
79
+ const count = pageNum === 1 ? 15 : 5;
80
+ return Array.from({ length: count }, (_, i) => {
81
+ const idx = (pageNum - 1) * 5 + i;
82
+ const otherUser = {
83
+ id: `user-${idx}`,
84
+ name: `Contact ${idx + 1}`,
85
+ imageUrl: `https://ui-avatars.com/api/?name=C${idx + 1}&background=6366f1&color=fff`,
86
+ };
87
+ return {
88
+ id: uuidv4(),
89
+ members: [currentUser, otherUser],
90
+ messageList: Array.from({ length: 10 }, (_, j) => ({
91
+ sender: j % 2 === 0 ? otherUser : currentUser,
92
+ messageBody: `Message ${j + 1} from page ${pageNum}`,
93
+ createdAt: dayjs()
94
+ .subtract(10 - j, "minutes")
95
+ .toDate(),
96
+ })),
97
+ };
73
98
  });
74
-
75
- handleSortConvoList(dupConvoList);
76
99
  };
77
100
 
78
- const handleSortConvoList = (convoList: Conversation[]) => {
79
- const sortedConvoList = convoList.sort((a, b) => {
80
- return moment(b.messageList[b.messageList.length - 1].createdAt).diff(
81
- moment(a.messageList[a.messageList.length - 1].createdAt)
82
- );
83
- });
84
- setConversationList(sortedConvoList);
85
- setConversation(sortedConvoList[0]);
86
- };
101
+ useEffect(() => {
102
+ // INFO: inject demo images of varying aspect ratios to exercise the bubble size cap
103
+ const demoImages = [
104
+ { id: 1015, w: 1600, h: 900, label: "Landscape (16:9)" },
105
+ { id: 1027, w: 900, h: 1600, label: "Portrait (9:16)" },
106
+ { id: 237, w: 1200, h: 1200, label: "Square (1:1)" },
107
+ ];
108
+ const withImages = seedConvoList.map((convo, idx) =>
109
+ idx === 0
110
+ ? {
111
+ ...convo,
112
+ messageList: [
113
+ ...convo.messageList,
114
+ ...demoImages.map((img) => ({
115
+ sender: convo.members.find((m) => m.id !== currentUser.id)!,
116
+ messageBody: {
117
+ fileName: `https://picsum.photos/id/${img.id}/${img.w}/${img.h}`,
118
+ token: img.label,
119
+ type: "image/jpeg",
120
+ },
121
+ createdAt: dayjs().subtract(demoImages.length - img.id, "minutes").toDate(),
122
+ })),
123
+ {
124
+ sender: convo.members.find((m) => m.id !== currentUser.id)!,
125
+ messageBody: {
126
+ fileName: "https://www.w3schools.com/html/mov_bbb.mp4",
127
+ token: "Sample video",
128
+ type: "video/mp4",
129
+ },
130
+ createdAt: dayjs().toDate(),
131
+ },
132
+ ],
133
+ }
134
+ : convo,
135
+ );
136
+ const sorted = sortByLatest(withImages);
137
+ setConversationList(sorted);
138
+ setConversation(sorted[0]);
139
+ setMetaConvoList(sorted);
140
+ setMetaConvo(sorted[0]);
141
+ const initial = generateConversations(1);
142
+ setPaginationConvoList(initial);
143
+ setPaginationConvo(initial[0]);
144
+ }, []);
87
145
 
88
- const handleMessageList = (id: string) => {
146
+ const handleSelectConversation = (id: string) => {
89
147
  setIsLoading(true);
90
148
  setMobileSelectedChat(true);
91
149
  setTimeout(() => {
92
150
  setIsLoading(false);
93
- conversationList.map((eachConversation) => {
94
- if (eachConversation.id === id) {
95
- setConversation(eachConversation);
96
- }
97
- return null;
98
- });
99
- }, 1000);
151
+ const found = conversationList.find((c) => c.id === id);
152
+ if (found) setConversation(found);
153
+ }, 800);
100
154
  };
101
155
 
102
- const renderBodyElement = () => {
103
- return isLoading ? (
104
- <div className="w-full">
105
- <Loading />
106
- </div>
107
- ) : undefined;
156
+ const handleSendMessage = (updated: Conversation) => {
157
+ setConversation(updated);
158
+ setMessage("");
159
+ setConversationList(
160
+ sortByLatest(
161
+ conversationList.map((c) => (c.id === updated.id ? updated : c)),
162
+ ),
163
+ );
108
164
  };
109
165
 
110
- const renderHeaderElement = () => {
111
- const otherUser = conversation.members.find((eachMember) => {
112
- return eachMember.id !== currentUser.id;
113
- });
114
- const userName = otherUser ? otherUser.name : "";
115
-
116
- return (
117
- <div className="w-full flex items-center h-12 text-white bg-primary-500 text-center">
118
- <IconButton
119
- icon={<ChevronLeftIcon className="w-6 h-6" />}
120
- background="bg-transparent"
121
- onClick={() => setMobileSelectedChat(false)}
122
- className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
123
- />
124
- <p className="ml-2 sm:ml-12">{userName}</p>
125
- </div>
166
+ const handleSearch = (searchMessage: string) => {
167
+ if (!searchMessage) {
168
+ setSearch("");
169
+ setFilteredConversation(null);
170
+ } else {
171
+ setFilteredConversation({
172
+ ...conversation,
173
+ messageList: conversation.messageList.filter(
174
+ (m) =>
175
+ typeof m.messageBody === "string" &&
176
+ m.messageBody.toLowerCase().includes(searchMessage.toLowerCase()),
177
+ ),
178
+ });
179
+ }
180
+ };
181
+
182
+ const handleSendUpload = (conv: Conversation, uploadedFile: File) => {
183
+ const fileUrl = URL.createObjectURL(uploadedFile);
184
+ const updated: Conversation = {
185
+ ...conv,
186
+ messageList: [
187
+ ...conv.messageList,
188
+ {
189
+ sender: currentUser,
190
+ messageBody: {
191
+ fileName: fileUrl,
192
+ token: uuidv4(),
193
+ type: uploadedFile.type,
194
+ },
195
+ createdAt: dayjs().toDate(),
196
+ },
197
+ ],
198
+ };
199
+ setConversation(updated);
200
+ setFile(undefined);
201
+ setConversationList(
202
+ sortByLatest(
203
+ conversationList.map((c) => (c.id === updated.id ? updated : c)),
204
+ ),
126
205
  );
127
206
  };
128
207
 
208
+ const otherUser = conversation.members.find((m) => m.id !== currentUser.id);
209
+
129
210
  return (
130
- <div>
131
- <div className="flex justify-center items-center py-2 space-x-4">
132
- <img src={Logo} alt="" className="h-14" />
133
- <h1 className="text-2xl text-primary-400">Chat App</h1>
134
- </div>
135
- <ChatDashboard
136
- value={message}
137
- searchMessage={search}
138
- currentUser={currentUser}
139
- selectedConversation={filteredConversation ?? conversation}
140
- conversationList={conversationList}
141
- mobileSelectedChat={mobileSelectedChat}
142
- sizeClassName="w-full h-[50vh] sm:h-[50vh]"
143
- chatBaseSizeClassName="w-full h-[50vh] sm:h-[50vh]"
144
- headerElement={renderHeaderElement()}
145
- bodyElement={renderBodyElement()}
146
- onChangeMessage={(e) => setMessage(e.target.value)}
147
- handleSelectedConversation={handleMessageList}
148
- onSendMessage={handleOnSendMessage}
149
- onChangeSearch={(e) => setSearch(e.target.value)}
150
- onSearch={handleOnSearch}
151
- />
152
- </div>
211
+ <>
212
+ {/* INFO: Section 1 — Base chat with file upload, search, actions */}
213
+ <StorySection
214
+ title="Base Chat"
215
+ description="Two-panel layout with conversation list, file upload, search, and actions. Mobile toggle via mobileSelectedChat."
216
+ >
217
+ <div className="w-full">
218
+ <ChatDashboard
219
+ value={message}
220
+ searchMessage={search}
221
+ currentUser={currentUser}
222
+ selectedConversation={filteredConversation ?? conversation}
223
+ conversationList={conversationList}
224
+ mobileSelectedChat={mobileSelectedChat}
225
+ sizeClassName="w-96 h-[60vh]"
226
+ chatBaseSizeClassName="w-full h-[60vh]"
227
+ headerElement={
228
+ <div className="w-full flex items-center h-12 text-white bg-primary-500">
229
+ <IconButton
230
+ icon={<ChevronLeftIcon className="w-6 h-6" />}
231
+ background="bg-transparent"
232
+ onClick={() => setMobileSelectedChat(false)}
233
+ className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
234
+ />
235
+ <p className="ml-2 sm:ml-4 font-medium">
236
+ {otherUser?.name ?? "Chat"}
237
+ </p>
238
+ </div>
239
+ }
240
+ bodyElement={
241
+ isLoading ? (
242
+ <div className="w-full h-full flex items-center justify-center">
243
+ <Loading />
244
+ </div>
245
+ ) : undefined
246
+ }
247
+ fileUpload={{
248
+ allowFileUpload: true,
249
+ file,
250
+ acceptFileType: ".jpg,.jpeg,.png,.mp4,.webm",
251
+ onChangeUpload: (e) => {
252
+ if (e?.target?.files?.length > 0) setFile(e.target.files[0]);
253
+ else setFile(undefined);
254
+ },
255
+ onSendUpload: handleSendUpload,
256
+ }}
257
+ leftPanelHeaderElement={
258
+ <div className="p-4">
259
+ <Button
260
+ type="normal"
261
+ size="small"
262
+ text="New Chat"
263
+ icon={<PencilSquareIcon className="w-4 h-4" />}
264
+ onClick={() => alert("New Chat clicked")}
265
+ className="w-full"
266
+ />
267
+ </div>
268
+ }
269
+ onChangeMessage={(e) => setMessage(e.target.value)}
270
+ onSendMessage={handleSendMessage}
271
+ onChangeSearch={(e) => setSearch(e.target.value)}
272
+ onSearch={handleSearch}
273
+ handleSelectedConversation={handleSelectConversation}
274
+ additionalAction={
275
+ [
276
+ {
277
+ text: "Clear Chat",
278
+ onClick: () =>
279
+ setConversation((prev) => ({ ...prev, messageList: [] })),
280
+ icon: <TrashIcon className="w-3 h-3" />,
281
+ },
282
+ ] as ChatBaseAction[]
283
+ }
284
+ />
285
+ </div>
286
+ </StorySection>
287
+
288
+ {/* INFO: Section 2 — renderConversationMeta + renderHeaderTags */}
289
+ <StorySection
290
+ title="Meta Badges & Header Tags"
291
+ description="renderConversationMeta shows status badges and 'Needs Reply' on left panel items. renderHeaderTags shows tags in the chat header."
292
+ >
293
+ <div className="w-full">
294
+ <ChatDashboard
295
+ value={metaMessage}
296
+ currentUser={currentUser}
297
+ selectedConversation={metaConvo}
298
+ conversationList={metaConvoList}
299
+ sizeClassName="w-96 h-[60vh]"
300
+ chatBaseSizeClassName="w-full h-[60vh]"
301
+ headerElement={
302
+ <div className="w-full flex items-center h-12 px-4 text-white bg-primary-500">
303
+ <p className="font-medium">
304
+ {metaConvo.members.find((m) => m.id !== currentUser.id)
305
+ ?.name ?? "Chat"}
306
+ </p>
307
+ </div>
308
+ }
309
+ renderConversationMeta={(convo) => {
310
+ const status =
311
+ DEMO_STATUSES[
312
+ Math.abs(convo.id.charCodeAt(0)) % DEMO_STATUSES.length
313
+ ];
314
+ const needsReply = convo.id.charCodeAt(0) % 2 === 0;
315
+ return (
316
+ <div className="flex gap-1 mt-1 flex-wrap">
317
+ <span
318
+ className={`text-[10px] px-1.5 py-0.5 rounded ${DEMO_BADGE_COLORS[status]}`}
319
+ >
320
+ {status}
321
+ </span>
322
+ {needsReply && (
323
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-orange-100 text-orange-700">
324
+ Needs Reply
325
+ </span>
326
+ )}
327
+ </div>
328
+ );
329
+ }}
330
+ renderHeaderTags={(convo) => {
331
+ const status =
332
+ DEMO_STATUSES[
333
+ Math.abs(convo.id.charCodeAt(0)) % DEMO_STATUSES.length
334
+ ];
335
+ return (
336
+ <div className="flex gap-1">
337
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-white/20 text-white">
338
+ {status}
339
+ </span>
340
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-white/20 text-white">
341
+ whatsapp
342
+ </span>
343
+ </div>
344
+ );
345
+ }}
346
+ onChangeMessage={(e) => setMetaMessage(e.target.value)}
347
+ onSendMessage={(updated) => {
348
+ setMetaConvo(updated);
349
+ setMetaMessage("");
350
+ setMetaConvoList((prev) =>
351
+ sortByLatest(
352
+ prev.map((c) => (c.id === updated.id ? updated : c)),
353
+ ),
354
+ );
355
+ }}
356
+ handleSelectedConversation={(id) => {
357
+ const found = metaConvoList.find((c) => c.id === id);
358
+ if (found) setMetaConvo(found);
359
+ }}
360
+ />
361
+ </div>
362
+ </StorySection>
363
+
364
+ {/* INFO: Section 3 — Pagination scroll callbacks */}
365
+ <StorySection
366
+ title="Pagination (Infinite Scroll)"
367
+ description="onConversationListScrollEnd loads more contacts with a spinner at the bottom. onMessageListScrollTop prepends older messages with a spinner at the top. Scroll to see both effects."
368
+ >
369
+ <div className="w-full">
370
+ <ChatDashboard
371
+ value={paginationMessage}
372
+ currentUser={currentUser}
373
+ selectedConversation={paginationConvo}
374
+ conversationList={paginationConvoList}
375
+ isLoadingConversations={loadingConversations}
376
+ isLoadingMessages={loadingMessages}
377
+ sizeClassName="w-96 h-[60vh]"
378
+ chatBaseSizeClassName="w-full h-[60vh]"
379
+ headerElement={
380
+ <div className="w-full flex items-center h-12 px-4 text-white bg-primary-500">
381
+ <p className="font-medium">
382
+ {paginationConvo.members.find(
383
+ (m) => m.id !== currentUser.id,
384
+ )?.name ?? "Chat"}
385
+ </p>
386
+ </div>
387
+ }
388
+ onConversationListScrollEnd={() => {
389
+ setLoadingConversations(true);
390
+ const nextPage = page + 1;
391
+ setTimeout(() => {
392
+ const more = generateConversations(nextPage);
393
+ setPaginationConvoList((prev) => [...prev, ...more]);
394
+ setPage(nextPage);
395
+ setLoadingConversations(false);
396
+ }, 1000);
397
+ }}
398
+ onMessageListScrollTop={() => {
399
+ setLoadingMessages(true);
400
+ const nextMsgPage = msgPage + 1;
401
+ setTimeout(() => {
402
+ setPaginationConvo((prev) => ({
403
+ ...prev,
404
+ messageList: [
405
+ ...Array.from({ length: 10 }, (_, i) => ({
406
+ sender:
407
+ i % 2 === 0
408
+ ? prev.members.find(
409
+ (m) => m.id !== currentUser.id,
410
+ )!
411
+ : currentUser,
412
+ messageBody: `Older message ${i + 1} from page ${nextMsgPage}`,
413
+ createdAt: dayjs()
414
+ .subtract(nextMsgPage * 10 + (10 - i), "minutes")
415
+ .toDate(),
416
+ })),
417
+ ...prev.messageList,
418
+ ],
419
+ }));
420
+ setMsgPage(nextMsgPage);
421
+ setLoadingMessages(false);
422
+ }, 1000);
423
+ }}
424
+ onChangeMessage={(e) => setPaginationMessage(e.target.value)}
425
+ onSendMessage={(updated) => {
426
+ setPaginationConvo(updated);
427
+ setPaginationMessage("");
428
+ setPaginationConvoList((prev) =>
429
+ sortByLatest(
430
+ prev.map((c) => (c.id === updated.id ? updated : c)),
431
+ ),
432
+ );
433
+ }}
434
+ handleSelectedConversation={(id) => {
435
+ const found = paginationConvoList.find((c) => c.id === id);
436
+ if (found) setPaginationConvo(found);
437
+ }}
438
+ />
439
+ </div>
440
+ </StorySection>
441
+ </>
153
442
  );
154
- });
443
+ }